본문 바로가기

네트워크 프로그래밍/Java NIO

[박혜웅] Queuing Strategies of java.util.concurrent.ThreadPoolExecutor

Work Queue에 가장 이상적인 형태로, 태스크를 멈추지(lock) 않고 기존 스레드나 새 스레드에서 수행하는 것이다.
태스크끼리 의존성이 있을 경우 유용하다.
태스크를 모두 수행해야 하므로, 제한없는(unbounded) maximumPoolSize가 필요하다.
하지만 스레드 수가 매우 빠르게 증가할 수 있다.
 
2. Unbounded queues (e.g. LinkedBlockingQueue without predefined capacity)
corePoolSize만큼의 모든 스레드가 바쁠 때, 태스크를 기다리게 한다.
새 스레드를 생성하지 않으므로 maximumPoolSize도 의미 없다.
웹서버처럼 각 태스크가 독립적인 경우 유용하다.
돌발적으로 갑자기 태스크가 급증하는 경우에 부드럽게 처리할 수 있는 장점이 있다.
하지만 큐에 대기하는 태스크가 매우 빠르게 증가할 수 있다.
* ThreadPoolExecutor는 사용하는 BlockingQueue를 생성할 때, 인자를 주지 않고 capacity를 정하지 않으므로 Unbounded queue처럼 동작한다.

3. Bounded queues (e.g. ArrayBlockingQueue)
CPU, OS, Context-Switching등의 리소스 소진(exhaustion)을 막는데 유용하다.
하지만 튜닝하기가 어렵다. 왜냐하면 큐 크기와 스레드풀 크기는 상호관계(trade off)가 있기 때문이다.
예를 들어, 큰 큐와 작은 풀을 사용할 경우, 리소스의 사용은 최소화할 수 있지만, 처리량(throuput)이 매우 작아진다.
예를 들어, 작은 큐와 큰 풀을 사용할 경우, CPU는 최대한 사용할 수 있지만, 많은 스레드에 의한 스케쥴링 오버헤드로 오히려 throuput 이 저하될 수 있다.