만들어놓은 스레드 풀을 초과하는 요청이 동시에 들어온다면 어떻게 될까?
지난 글에서의 고민에서 시작되어서 멀티 쓰레드 상황에 동시성 문제가 생기면 어떻게 쓰레드 풀을 관리해야되는지 궁금증이 생겨서 찾아본 내용들을 정리해봤다.
- 기본 설정된 개수 만큼 쓰레드 생성
- 기본 생성 스레드 수를 초과하는 요청이 들어오면 작업이 큐에 쌓인다.
- 큐의 용량 수를 초과하면 그때 쓰레드를 추가 생성한다.
corePoolSize
: 8
- 쓰레드 풀의 기본 쓰레드 수 (항상 유지)
maxPoolSize
: Integer.MAX_VALUE
- 최대 쓰레드 수,
corePoolSize
를 초과하는 요청이 들어오고 큐가 가득 찰 때까지 쓰레드 수를 늘릴 수 있다.queueCapacity
: Integer.MAX_VALUE
- 쓰레드가 바쁘면 요청을 대기열에 넣을 수 있다. 대기열이 가득 차면 새로운 쓰레드 생성
keepAliveSeconds
: 60
- 추가로 생성된 쓰레드가 유휴 상태로 유지되는 시간, 시간이 지나면 쓰레드가 제거된다.
만들어놓은 스레드 풀을 초과하는 요청이 동시에 들어온다면 어떻게 될까?
첫 물음에서 queueCapacity
가 Integer.MAX_VALUE 값을 가지고 있기 때문에 기본 설정에서는 웬만하면 추가적인 쓰레드를 생성할 일이 없다.
상황에 따른 쓰레드풀 관리가 중요해 보인다.
@Configuration
public class ThreadPoolConfig {
@Bean
public ThreadPoolTaskExecutor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(10);
executor.setMaxPoolSize(20);
executor.setQueueCapacity(50);
executor.setKeepAliveSeconds(60);
executor.setThreadNamePrefix("MyExecutor-");
executor.initialize();
return executor;
}
}
위와 같은 방식으로 스레드 풀을 커스터마이징 할 수 있다.
참고로 keepAliveTime
설정은 유휴 상태가 지속되면 종료될 수 있게 만든다. 이는 쓰레드 풀이 더 이상 필요하지 않은 쓰레드를 해제하여 자원을 절약하기 위한 메커니즘이다.
개인적으로 두가지가 더 궁금해진다.
1. queueCapacity
를 0으로만들고 maxPoolSize
를 무한히 유지하면 굳이 큐에 대기할 필요없지 않을까?
2. 요청이 예상보다 너무 많아서 maxPoolSize
를 초과하면 어떻게 될까?
당연히 시스템 자원적인 측면에서 좋아보이지는 않아보인다.
실제로 쓰레드를 생성할 때마다 메모리
와 CPU 자원
이 필요한데 쓰레드의 스택 크기는 보통 KB에서 MB까지 할당되므로 많아질수록 메모리 소모가 커지고, 쓰레드가 많아지면 CPU 스케줄링에 대한 오버헤드가 증가하고 컨텍스트 스위칭이 빈번해지면서 자원 소모
OutOfMemoryError
발생 가능성 농후
쓰레드 풀을 사용하는 이유는 쓰레드 재사용을 통해 자원을 절약하고 성능을 최적화하기 위한 장치 제한된 수의 쓰레드를 관리하면서 Queue를 통해 요청을 적절히 조절하는게 효율적
corePoolSize
: 10queueCapacity
: 50maxPoolSize
: 100
쓰레드 풀 설정에서 만약 500개의 동시 요청이 일어났다고 가정했을때 순서대로 일어나는 일을 요약해보면
corePoolSize
10개의 쓰레드가 작업 진행queueCapacity
50개 만큼의 요청 대기maxPoolSize
100을 채우기 위해 90개의 쓰레드 생성queue
에 쌓여있는 작업 50개와 새로운 40개의 작업 새 생성된 쓰레드에 요청queue
가 비어있는 상태이므로 새로운 작업 50개가 큐에 쌓임AbortPolicy
: 기본 정책으로 예외를 발생시킴CallerRunsPolicy
: 현재 실행 중인 스레드가 직접 작업을 처리DiscardPolicy
: 처리할 수 없는 요청을 무시합니다.DiscardOldestPolicy
: 가장 오래된 요청을 대기열에서 제거하고, 새로운 요청 추가