스레드 풀이란?
스레드 풀은 일종의 작업 스레드 관리 컨테이너로 이해할 수 있다. 애플리케이션이 시작될 때 미리 여러 개의 스레드를 생성하여 풀 안에 보관하고, 작업이 필요할 때마다 새로 스레드를 만들지 않고 이 풀에서 꺼내 사용한다. 작업이 끝나면 스레드는 다시 풀로 반환되어 다음 작업을 기다린다.
기존 방식의 문제점
자바 1.5 이전에는 각각의 작업마다 new Thread()를 통해 새 스레드를 생성해야 했다. 작업 완료 후 해당 스레드는 소멸되므로, 반복적인 생성과 소멸 과정에서 상당한 리소스 오버헤드가 발생했다. 이는 특히 고빈도로 작업이 발생하는 환경에서 성능 저하의 주요 원인이 되었다.
JDK 1.5부터 java.util.concurrent 패키지는 스레드 풀 기반의 동시성 처리를 지원하게 되었으며, 이를 통해 스레드 자원을 효율적으로 재사용할 수 있게 되었다.
스레드 풀 사용의 장점
- 성능 향상: 스레드 생성 및 종료 비용을 줄여 응답 속도를 개선한다.
- 자원 최적화: 제한된 스레드 수로 시스템 자원을 효율적으로 관리한다.
- 중앙 집중식 관리: 스레드 생명주기와 작업 큐를 통합 관리할 수 있다.
스레드 풀 생성 방법
자바에서는 Executors 유틸리티 클래스를 제공하지만, 알리바바 코딩 가이드라인 등에서는 더 명시적인 제어를 위해 ThreadPoolExecutor 직접 생성을 권장한다.
public class Task implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " - 작업 실행 중");
}
}
public class ThreadPoolExample {
public static void main(String[] args) {
// ThreadPoolExecutor 직접 생성
ThreadPoolExecutor executor = new ThreadPoolExecutor(
3, // 핵심 스레드 수 (corePoolSize)
8, // 최대 스레드 수 (maximumPoolSize)
60L, // 유휴 시간 (keepAliveTime)
TimeUnit.SECONDS, // 시간 단위
new LinkedBlockingQueue<>(10) // 작업 큐
);
// 12개의 작업 제출
for (int i = 0; i < 12; i++) {
executor.execute(new Task());
}
executor.shutdown(); // 정상 종료 요청
}
}
ThreadPoolExecutor 주요 파라미터 설명
corePoolSize – 핵심 스레드 수
풀 내 상시 유지되는 최소 스레드 수다. 새로운 작업이 들어오면 우선 이 수준까지는 새 스레드를 생성하여 즉시 처리한다. 기본 설정에서는 이 수치 이하의 스레드는 시간이 지나도 종료되지 않는다.
maximumPoolSize – 최대 스레드 한도
풀이 동적으로 생성할 수 있는 전체 스레드의 최대치다. 작업 큐가 가득 찬 후에도 남은 작업이 있으면 이 한도까지 추가 스레드를 생성해 처리한다.
keepAliveTime – 유휴 시간
핵심 수(corePoolSize)를 초과한 임시 스레드가 작업 없이 대기할 수 있는 시간이다. 이 시간이 지나면 해당 스레드는 종료된다. allowCoreThreadTimeOut(true) 설정 시 핵심 스레드에도 적용 가능하다.
workQueue – 작업 대기 큐
현재 사용 가능한 스레드가 없을 경우, 새 작업은 이 큐에 저장되어 실행 가능 상태가 될 때까지 대기한다. 대표적인 큐 구현체로는 ArrayBlockingQueue, LinkedBlockingQueue, SynchronousQueue 등이 있다.
작업 처리 흐름 요약
- 새 작업 제출
- 현재 활성 스레드 수가
corePoolSize보다 작으면 새 스레드 생성 후 실행 - 그렇지 않으면 작업 큐에 삽입
- 큐가 가득 찼고, 현재 스레드 수가
maximumPoolSize미만이면 임시 스레드 생성 - 모두 포화 상태라면 거부 전략(Rejection Policy)에 따라 처리
이러한 메커니즘은 시스템 부하에 유연하게 대응하면서도 자원 낭비를 방지하는 데 효과적이다. 다음에는 다양한 거부 전략에 대해 살펴볼 예정이다.