Tomcat과 같은 thread per request
전략을 사용하는 애플리케이션에 많은 요청이 밀려들었다. 그 때마다 thread를 생성하고 소멸시킨다면 자원이 많이 낭비되지 않을까?
이 문제를 해결하기 위한 방법 중 하나로 Thread Pool
이 있다. 미리 Thread를 생성해두고, Task를 수행할 Thread를 할당하고 수거함으로써 자원 낭비를 줄일 수 있게 된다.
이처럼 생성/소멸에 자원이 많이 소모되는 환경에서 이를 미리 만들어 둔 그룹을
pool
이라고 한다.
Thread Pool
뿐 아니라 DB와의Connection
을 만들어 둔Connection Pool
도 있다.
그렇다면 Java에서 이런 Thread Pool
을 어떻게 생성하고 관리할까?
Java 5를 기점으로 Executor
, ExecutorService
라는 인터페이스가 등장했다.
public interface Executor {
void execute(Runnable runnable);
}
Executor
는 Thread Pool
에 Task를 실행시키는 추상화, Functional interface
형식ExecutorService extends Executor
는 Task 실행에서 더 나아가 Thread Pool
이 가져야 할 기능의 추상화이다.우리는 이 인터페이스의 구현체 중 하나인 ThreadPoolExecutor
살펴보자.
일단 ThreadPoolExecutor
의 계층 구조는 위와 같다.
객체 생성 방식은 크게 두 가지이다.
- new + 생성자
Executors
의 팩토리 메서드 (single
/fixed
/cached
)
우리는 핵심이 되는 "thread가 생성되고 소멸되는 과정" 을 알아보자.
어차피 Executors
에 의한 3가지 ThreadPoolExecutor
는 corePoolSize
, MaxPoolSize
, keepAlive
인자 차이일 뿐이다.
corePoolSize
: 쓰레드 풀이 유지할 쓰레드 수maxPoolSize
: 쓰레드 풀이 가질 수 있는 최대 쓰레드 수keepAlive
: 쓰레드가 task 종료 후 살아있을 시간 (corePoolSize
이상 일 때)queueSize
: waitingQueue 사이즈active thread가 corePoolSize
미만이라면, 새 thread를 생성
corePoolSize
<= active <= MaxPoolSize
라면 Task를 Queue
에 추가
이때 Queue
가 가득 찼다면 새 thread 생성
단, active < MaxPoolSize
일 경우에만이다.
Queue
가 가득 차고, thread 수가 maxPoolSize
이라면 해당 Task는 Reject
가 된다. ... RejectedExecutionException
여기서 keepAlive
인자가 사용된다. Thread Pool
은 불필요한 자원 소모를 방지하기 위해, corePoolSize
를 넘는 Thread는 Task 종료 후 keepAlive
가 지나면 이 thread를 소멸시킨다.
주요한 특징은
corePoolSize
유지 &새 Thread 생성 보다는 Queue에 추가