ThreadPool은 멀티 스레드를 이용해야할때 Thread를 효율적으로 관리하기 위해 사용합니다.
Thread를 효율적으로 관리한다는 말은 어떤뜻일까요?
저는 ThreadPool을 한마디로 정의하면 아래와 같이 말할것 같습니다.
Thread를 재활용하기 위한 저장소
주의 : QueueCapacity
그럼 ThreadPool에 대해 자세히 알아보겠습니다.
ThreadPool을 사용하지 않을때
Context Switching
java는 One-to-One Threading-Model로 Thread를 생성합니다.
즉, OS Thread와 UserThread를 1:1로 생성하는 것입니다.
그렇다면 Thread를 생성할때마다 OS kernel의 작업이필요하게됩니다.
즉, Context Switching이 일어나게 되는데 이는 매우 비용이 많이드는 작업입니다.
메모리, CPU 오버헤드
Thread의 생성 요청이 애플리케이션의 처리 속도보다 빠르게 온다면
새로운 Thread가 무한으로 생성될것입니다.
이는 메모리문제와 CPU 오버헤드를 유발합니다.
ThreadPool 사용
위의 문제는 Thread를 재활용하여 해결할 수 있습니다.

ThreadPool 사용법
전체적인 흐름은 아래의 그림과 같습니다.

- ThreadPool은 Tomcat에 첫 요청이 들어오면 Thread의 corePoolSize 갯수만큼 Thread를 생성합니다.
- 요청시마다 ThreadPool에서 Thread를 할당합니다.
- Thread가 작업을 완료하면 다시 ThreadPool로 돌아옵니다.
ThreadPool로 인해 Thread를 첫요청시에만 생성하고, 더 사용하지 않고있다는것을 알 수 있습니다.
하지만 ThreadPool을 사용할때는 설정에 주의하여야 합니다.
ThreadPool 설정
ThreadPool은 아래와 같은 설정 옵션이 있습니다.
ThreadPool은 메모리와 CPU에대한 고려를 하면서 사용해야 합니다.
- corePoolSize
- ThreadPool은 corePoolSize만큼의 스레드를 유지합니다.
- maximumPoolsize
- 최대 ThreadPool의 들어갈 수 있는 Thread의 사이즈입니다.
- 대기중인 Thread가 QueueCapacity를 초과하면 Thread를 maximumPoolsize까지 추가로 생성합니다.
- QueueCapacity
- 요청이 들어왔을때 Thread가 대기중인 workQueue의 사이즈입니다.
- workQueue의 사이즈가 초과되면 maximumPoolsize만큼 Thread가 생성됩니다.
- keepAliveTime
- keepAliveTime에 설정된 시간만큼 요청이 없다면 ThreadPool의 Thread는 CorePoolSize로 줄어듭니다.
요청 Flow ( QueueCapacity설정이 중요한 이유)
corePoolSize -> workQueue -> maximumPoolSize
corePoolSize의 모든 Thread가 busy 상태인 경우
새로운 task는 maximumPoolSize로 확장되는것이 아니라 workQueue에 들어갑니다.
workQueue까지도 다 차면 그때 maximumPoolSize만큼 ThreadPool에 Thread를 생성하고
생성된 Thread를 할당받습니다.
🎯그러므로 QueueCapacity 의 크기를 크게 잡거나 크기지정을 하지 않는다면
쓰레드 풀은 maximumPoolSize로 확장하지 않기 때문에
실질적으로는 maximumPoolSize는 효과가 없어지게 됩니다.
Executors가 생성해주는 ExecutorService
Executors가 생성해주는 ExecutorService는 흔히 ThreadPool(쓰레드풀)이라고 불리며 다음과 같은 것들이 있습니다.
- newFixedThreadPool(int) : 인자 갯수만큼 고정된 쓰레드풀을 생성한다.
- newCachedThreadPool(): 필요할 때, 필요한 만큼 쓰레드풀을 생성한다. 이미 생성된 쓰레드를 재활용할 수 있기 때문에 성능상의 이점이 있을 수 있다.
- newScheduledThreadPool(int): 일정 시간 뒤에 실행되는 작업이나, 주기적으로 수행되는 쓰레드풀을 인자 갯수만큼 생성한다.
- newSingleThreadExecutor(): 단일 쓰레드인 풀을 생성한다. 단일 쓰레드에서 동작해야 하는 작업을 처리할 때 사용한다.
- newWorkStealingPool(int parallelism): 시스템에 가용 가능한 만큼 쓰레드를 활용하는 ExecutorService를 생성한다.
QueueCapacity의 크기에 따른 장단점
QueueCapacity 높을경우
QueueCapacity가 높다면 시간은 조금 오래걸리더라도 새로운 스레드를 생성하지 않고
wordQueue에 대기하다가 coreThread를 재활용 하기 때문에
새로운 스레드를 만드는데 메모리를와 Context Switching을 하지 않기때문에
메모리면에서 효율적입니다.
반면 시간에서는 비효율적입니다.
QueueCapacity 낮은 경우
QueueCapacity가 낮다면 wordQueue가 다 꽉차버릴경우
maximumPoolsize까지 스레드를 필요한만큼 늘려 일처리를 바로 할 수 있고 병목이 생길 확률이 줄어듭니다.
시간은 적게 걸리지만 새로운 스레드들을 생성하기 때문에 새로운 스레드를 만드는데 메모리를 더 쓰게 될 것입니다.
Project에 적용해 보기
@Configuration
public class ThreadPool {
private static final int CORE_POOL_SIZE = 3;
private static final int QUEUE_CAPACITY = 3;
private static final int MAX_POOL_SIZE = 100;
private static final int KEEP_ALIVE_SECONDS = 1;
private static final String NAME_PREFIX = "customAsyncTask-";
@Bean
public Executor getDomainEventTaskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(CORE_POOL_SIZE);
executor.setQueueCapacity(QUEUE_CAPACITY);
executor.setMaxPoolSize(MAX_POOL_SIZE);
executor.setKeepAliveSeconds(KEEP_ALIVE_SECONDS);
executor.setThreadNamePrefix(NAME_PREFIX);
return executor;
}
}
사용자 요청에 빠른 응답과 메모리를 적게 사용하기위해
corePoolSize와 QueueCapacity를 낮게 잡고 MaxPoolSize를 높게잡아
스레드를 생성하고 소멸시키면서 생기는 오버헤드보다는 메모리를 적게 사용하는 것을 채택하였습니다.
ThreadPool을 Custom한 이유
Executors가 생성해주는 ExecutorService는 원하는 ThreadPool을 만들수 없어 직접 Custom하였습니다.
느낀점
ThreadPool에 기본 개념과 설정을 잘알고 사용해야
더 좋은 개발자가 될 수 있다는것을 알게되었습니다!
참고
ThreadPool Flow - QueueCapacity가 중요한 이유
Spring의 Thread Pool전략 - maximumPoolSize작동 안하는 경우
쉬운코드 - 스레드 풀(thread pool)
ExecutorService를-이용해-multi-thread-활용하기-Java
ThreadPoolExecutor에서 corePoolSize와 queue capacity의 관계
ThreadPoolExecutor는 ExecutorService를 상속받은 클래스로Task(Runnable)를 저장하는 BlockingQueue와 이를 수행하는 ThreadPoolSize를 설정할 수 있다. ThreadPoolExecutor 생성자에서 corePoolSize와 maximumPoolSize, BlockingQueue
dskim98.tistory.com
'Java' 카테고리의 다른 글
Lambda, Stream (0) | 2023.10.11 |
---|---|
Error와 Exception의 차이 (0) | 2023.10.10 |
자바 컬렉션 (0) | 2023.09.27 |
제네릭 사용 이유 (0) | 2023.09.26 |
java.lang 패키지 (0) | 2023.09.26 |
ThreadPool은 멀티 스레드를 이용해야할때 Thread를 효율적으로 관리하기 위해 사용합니다.
Thread를 효율적으로 관리한다는 말은 어떤뜻일까요?
저는 ThreadPool을 한마디로 정의하면 아래와 같이 말할것 같습니다.
Thread를 재활용하기 위한 저장소
주의 : QueueCapacity
그럼 ThreadPool에 대해 자세히 알아보겠습니다.
ThreadPool을 사용하지 않을때
Context Switching
java는 One-to-One Threading-Model로 Thread를 생성합니다.
즉, OS Thread와 UserThread를 1:1로 생성하는 것입니다.
그렇다면 Thread를 생성할때마다 OS kernel의 작업이필요하게됩니다.
즉, Context Switching이 일어나게 되는데 이는 매우 비용이 많이드는 작업입니다.
메모리, CPU 오버헤드
Thread의 생성 요청이 애플리케이션의 처리 속도보다 빠르게 온다면
새로운 Thread가 무한으로 생성될것입니다.
이는 메모리문제와 CPU 오버헤드를 유발합니다.
ThreadPool 사용
위의 문제는 Thread를 재활용하여 해결할 수 있습니다.

ThreadPool 사용법
전체적인 흐름은 아래의 그림과 같습니다.

- ThreadPool은 Tomcat에 첫 요청이 들어오면 Thread의 corePoolSize 갯수만큼 Thread를 생성합니다.
- 요청시마다 ThreadPool에서 Thread를 할당합니다.
- Thread가 작업을 완료하면 다시 ThreadPool로 돌아옵니다.
ThreadPool로 인해 Thread를 첫요청시에만 생성하고, 더 사용하지 않고있다는것을 알 수 있습니다.
하지만 ThreadPool을 사용할때는 설정에 주의하여야 합니다.
ThreadPool 설정
ThreadPool은 아래와 같은 설정 옵션이 있습니다.
ThreadPool은 메모리와 CPU에대한 고려를 하면서 사용해야 합니다.
- corePoolSize
- ThreadPool은 corePoolSize만큼의 스레드를 유지합니다.
- maximumPoolsize
- 최대 ThreadPool의 들어갈 수 있는 Thread의 사이즈입니다.
- 대기중인 Thread가 QueueCapacity를 초과하면 Thread를 maximumPoolsize까지 추가로 생성합니다.
- QueueCapacity
- 요청이 들어왔을때 Thread가 대기중인 workQueue의 사이즈입니다.
- workQueue의 사이즈가 초과되면 maximumPoolsize만큼 Thread가 생성됩니다.
- keepAliveTime
- keepAliveTime에 설정된 시간만큼 요청이 없다면 ThreadPool의 Thread는 CorePoolSize로 줄어듭니다.
요청 Flow ( QueueCapacity설정이 중요한 이유)
corePoolSize -> workQueue -> maximumPoolSize
corePoolSize의 모든 Thread가 busy 상태인 경우
새로운 task는 maximumPoolSize로 확장되는것이 아니라 workQueue에 들어갑니다.
workQueue까지도 다 차면 그때 maximumPoolSize만큼 ThreadPool에 Thread를 생성하고
생성된 Thread를 할당받습니다.
🎯그러므로 QueueCapacity 의 크기를 크게 잡거나 크기지정을 하지 않는다면
쓰레드 풀은 maximumPoolSize로 확장하지 않기 때문에
실질적으로는 maximumPoolSize는 효과가 없어지게 됩니다.
Executors가 생성해주는 ExecutorService
Executors가 생성해주는 ExecutorService는 흔히 ThreadPool(쓰레드풀)이라고 불리며 다음과 같은 것들이 있습니다.
- newFixedThreadPool(int) : 인자 갯수만큼 고정된 쓰레드풀을 생성한다.
- newCachedThreadPool(): 필요할 때, 필요한 만큼 쓰레드풀을 생성한다. 이미 생성된 쓰레드를 재활용할 수 있기 때문에 성능상의 이점이 있을 수 있다.
- newScheduledThreadPool(int): 일정 시간 뒤에 실행되는 작업이나, 주기적으로 수행되는 쓰레드풀을 인자 갯수만큼 생성한다.
- newSingleThreadExecutor(): 단일 쓰레드인 풀을 생성한다. 단일 쓰레드에서 동작해야 하는 작업을 처리할 때 사용한다.
- newWorkStealingPool(int parallelism): 시스템에 가용 가능한 만큼 쓰레드를 활용하는 ExecutorService를 생성한다.
QueueCapacity의 크기에 따른 장단점
QueueCapacity 높을경우
QueueCapacity가 높다면 시간은 조금 오래걸리더라도 새로운 스레드를 생성하지 않고
wordQueue에 대기하다가 coreThread를 재활용 하기 때문에
새로운 스레드를 만드는데 메모리를와 Context Switching을 하지 않기때문에
메모리면에서 효율적입니다.
반면 시간에서는 비효율적입니다.
QueueCapacity 낮은 경우
QueueCapacity가 낮다면 wordQueue가 다 꽉차버릴경우
maximumPoolsize까지 스레드를 필요한만큼 늘려 일처리를 바로 할 수 있고 병목이 생길 확률이 줄어듭니다.
시간은 적게 걸리지만 새로운 스레드들을 생성하기 때문에 새로운 스레드를 만드는데 메모리를 더 쓰게 될 것입니다.
Project에 적용해 보기
@Configuration
public class ThreadPool {
private static final int CORE_POOL_SIZE = 3;
private static final int QUEUE_CAPACITY = 3;
private static final int MAX_POOL_SIZE = 100;
private static final int KEEP_ALIVE_SECONDS = 1;
private static final String NAME_PREFIX = "customAsyncTask-";
@Bean
public Executor getDomainEventTaskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(CORE_POOL_SIZE);
executor.setQueueCapacity(QUEUE_CAPACITY);
executor.setMaxPoolSize(MAX_POOL_SIZE);
executor.setKeepAliveSeconds(KEEP_ALIVE_SECONDS);
executor.setThreadNamePrefix(NAME_PREFIX);
return executor;
}
}
사용자 요청에 빠른 응답과 메모리를 적게 사용하기위해
corePoolSize와 QueueCapacity를 낮게 잡고 MaxPoolSize를 높게잡아
스레드를 생성하고 소멸시키면서 생기는 오버헤드보다는 메모리를 적게 사용하는 것을 채택하였습니다.
ThreadPool을 Custom한 이유
Executors가 생성해주는 ExecutorService는 원하는 ThreadPool을 만들수 없어 직접 Custom하였습니다.
느낀점
ThreadPool에 기본 개념과 설정을 잘알고 사용해야
더 좋은 개발자가 될 수 있다는것을 알게되었습니다!
참고
ThreadPool Flow - QueueCapacity가 중요한 이유
Spring의 Thread Pool전략 - maximumPoolSize작동 안하는 경우
쉬운코드 - 스레드 풀(thread pool)
ExecutorService를-이용해-multi-thread-활용하기-Java
ThreadPoolExecutor에서 corePoolSize와 queue capacity의 관계
ThreadPoolExecutor는 ExecutorService를 상속받은 클래스로Task(Runnable)를 저장하는 BlockingQueue와 이를 수행하는 ThreadPoolSize를 설정할 수 있다. ThreadPoolExecutor 생성자에서 corePoolSize와 maximumPoolSize, BlockingQueue
dskim98.tistory.com
'Java' 카테고리의 다른 글
Lambda, Stream (0) | 2023.10.11 |
---|---|
Error와 Exception의 차이 (0) | 2023.10.10 |
자바 컬렉션 (0) | 2023.09.27 |
제네릭 사용 이유 (0) | 2023.09.26 |
java.lang 패키지 (0) | 2023.09.26 |