스레드 관리의 핵심: 실행자 패턴 소개
기존의 Thread와 Runnable을 직접 사용하는 방식은 스레드 생성/종료의 부담이 크며, 성능 저하를 초래할 수 있습니다. 대신 Executor 계층 구조를 활용하면 스레드의 생명주기 관리와 작업 제출을 분리할 수 있습니다.
실행자 인터페이스의 계층 구조
Executor: 단순한 작업 제출 기능만 제공 (execute(Runnable))ExecutorService:Executor를 확장하며 종료 제어 및 작업 결과 조회 기능 추가Executors: 정적 팩토리 메서드를 통해 다양한 유형의 스레드 풀 생성 가능
스레드 풀의 주요 유형
// 자동 스케일링 가능한 풀 (필요 시 새 스레드 생성)
ExecutorService cachedPool = Executors.newCachedThreadPool();
// 고정된 크기의 스레드 풀
ExecutorService fixedPool = Executors.newFixedThreadPool(4);
// 단일 스레드 실행자 (순차 처리 보장)
ExecutorService singleThread = Executors.newSingleThreadExecutor();
// 지연 또는 반복 실행 가능한 스케줄러
ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
비동기 작업: Callable과 Future
Callable은 Runnable과 달리 작업 결과를 반환할 수 있는 인터페이스입니다. 이는 비동기 연산의 결과를 얻는 데 필수적입니다.
class DataProcessingTask implements Callable<String> {
@Override
public String call() throws Exception {
Thread.sleep(1000);
return "Processed by " + Thread.currentThread().getName();
}
}
Future는 비동기 작업의 상태와 결과를 추적하는 역할을 합니다. 주요 메서드는 다음과 같습니다:
get(): 결과가 준비될 때까지 블로킹 대기isDone(): 작업 완료 여부 확인cancel(boolean mayInterruptIfRunning): 작업 취소 요청
ExecutorService executor = Executors.newFixedThreadPool(2);
Callable<String> task = new DataProcessingTask();
Future<String> result = executor.submit(task);
// 비동기 상태 체크
while (!result.isDone()) {
System.out.println("작업 중...");
Thread.sleep(500);
}
System.out.println("결과: " + result.get());
executor.shutdown();
FutureTask: Runnable과 Future의 결합체
FutureTask는 Runnable과 Future를 동시에 구현한 클래스로, 다음과 같은 특징을 가집니다:
- 직접
Thread에 전달해 실행 가능 - 결과를
get()으로 비동기적으로 가져올 수 있음 - 스레드 풀에 제출할 수 있는 일반적인 작업 객체
Callable<Integer> calculator = () -> {
Thread.sleep(2000);
return 1 + 1;
};
FutureTask<Integer> task = new FutureTask<>(calculator);
// 별도 스레드로 실행
new Thread(task).start();
// 결과 획득 (블로킹)
Integer answer = task.get();
System.out.println("계산 결과: " + answer);
또한 ExecutorService에 submit(FutureTask) 형태로 제출 가능하여, 기존의 Runnable 기반 코드와도 호환됩니다.
권장 사항 및 주의사항
- 스레드 풀 사용 후 반드시
shutdown()호출하여 리소스 해제 - 작업 결과를 기다릴 때
get()은 블로킹이므로 적절한 타임아웃 설정 권장 - 실제 운영 환경에서는
Executors의 정적 팩토리보다ThreadPoolExecutor직접 구성이 더 안정적