ThreadPoolExecutor
ThreadPoolExecutor (쓰레드 풀 실행기)
제출된 각 작업을 여러 개일 수 있는 풀링된 쓰레드 중 하나로 실행하는 ExecutorService예요. 보통 Executors 팩토리 메서드로 구성해요. 즉시 사용 가능한 다양한 조정 파라미터와 확장 훅을 제공하지만, 대부분의 상황에서는 Executors 팩토리 메서드를 쓰는 게 편리해요.
본문
쓰레드 풀은 두 가지 문제를 해결해요. 많은 비동기 작업을 실행할 때 작업당 호출 오버헤드가 줄어 성능이 좋아지고, 작업 수행에 소비되는 쓰레드 자원을 경계 짓고 관리하는 수단을 제공해요. 각 ThreadPoolExecutor는 완료된 작업 수 같은 기본 통계도 유지해요.
가장 흔한 사용 시나리오는 이미 설정이 되어 있는 Executors 팩토리 메서드로 처리돼요.
ExecutorService pool = Executors.newCachedThreadPool(); // 무제한 풀, 자동 재생
ExecutorService fixed = Executors.newFixedThreadPool(4); // 고정 크기 풀
ExecutorService single = Executors.newSingleThreadExecutor(); // 단일 백그라운드 쓰레드
직접 구성할 때는 다음 지침을 참고해요.
코어·최대 풀 크기 — ThreadPoolExecutor는 corePoolSize와 maximumPoolSize의 경계에 따라 풀 크기를 자동 조절해요. execute로 새 작업이 들어오면 실행 중인 쓰레드가 corePoolSize보다 적으면 새 쓰레드를 만들고, 그보다 많으면 큐가 가득 찬 경우에만 새 쓰레드를 만들어요. 두 크기를 같게 하면 고정 크기 풀이 돼요. 크기는 setCorePoolSize(int)와 setMaximumPoolSize(int)로 동적으로 바꿀 수 있어요.
주문형 생성 — 기본적으로 코어 쓰레드도 새 작업이 도착할 때만 생성·시작돼요. prestartCoreThread()나 prestartAllCoreThreads()로 미리 시작할 수 있어요.
새 쓰레드 생성 — 쓰레드는 ThreadFactory로 만들어져요. 기본은 Executors.defaultThreadFactory()로, 모든 쓰레드가 같은 ThreadGroup·NORM_PRIORITY·비데몬 상태를 갖게 해요. 쓰레드는 "modifyThread" RuntimePermission을 가져야 해요.
유지 시간(keep-alive) — keepAliveTime보다 오래 유휴 상태인 초과 쓰레드는 종료돼요. setKeepAliveTime(long, TimeUnit)으로 동적으로 조정 가능하고, allowCoreThreadTimeOut(boolean)으로 코어 쓰레드에도 적용할 수 있어요.
큐잉 — 작업 전달과 보관에 아무 BlockingQueue나 쓸 수 있어요.
- 직접 핸드오프:
SynchronousQueue가 기본값으로, 쓰레드가 즉시 없으면 큐잉에 실패해 새 쓰레드가 생겨요. - 무제한 큐:
LinkedBlockingQueue처럼 용량이 없으면 코어 쓰레드만 생성돼요. - 경계 큐:
ArrayBlockingQueue는 유한maximumPoolSize와 함께 리소스 고갈을 막는 데 도움돼요.
거부된 작업 — Executor가 종료됐거나 포화 상태면 RejectedExecutionHandler.rejectedExecution이 호출돼요. 네 가지 기본 정책이 있어요.
AbortPolicy(기본): 거부 시 런타임RejectedExecutionException을 던져요.CallerRunsPolicy:execute를 호출한 쓰레드가 직접 작업을 실행해요.DiscardPolicy: 실행 불가능한 작업을 그냥 버려요.DiscardOldestPolicy: 작업 큐의 맨 앞 작업을 버리고 재시도해요.
훅 메서드 — 보호 메서드 beforeExecute(Thread, Runnable)와 afterExecute(Runnable, Throwable)를 재정의해 실행 전후에 환경을 조작할 수 있어요. terminated()는 완전 종료 후 처리에 쓸 수 있어요.
class PausableThreadPoolExecutor extends ThreadPoolExecutor {
private boolean isPaused;
private ReentrantLock pauseLock = new ReentrantLock();
private Condition unpaused = pauseLock.newCondition();
public PausableThreadPoolExecutor(...) { super(...); }
protected void beforeExecute(Thread t, Runnable r) {
super.beforeExecute(t, r);
pauseLock.lock();
try { while (isPaused) unpaused.await(); }
catch (InterruptedException ie) { t.interrupt(); }
finally { pauseLock.unlock(); }
}
public void pause() { pauseLock.lock(); try { isPaused = true; } finally { pauseLock.unlock(); } }
public void resume() { pauseLock.lock(); try { isPaused = false; unpaused.signalAll(); } finally { pauseLock.unlock(); } }
}
getQueue()는 모니터링·디버깅 목적으로만 사용하길 권장해요. 큰 수의 큐잉 작업이 취소될 때 공간을 회수하는 데 remove(Runnable)와 purge()가 있어요. 코드와 시그니처는 원문 그대로 보존돼요.