Java에서의 스레드 풀과 비동기 작업 처리: Executor, Callable, Future, FutureTask

스레드 관리의 핵심: 실행자 패턴 소개

기존의 ThreadRunnable을 직접 사용하는 방식은 스레드 생성/종료의 부담이 크며, 성능 저하를 초래할 수 있습니다. 대신 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

CallableRunnable과 달리 작업 결과를 반환할 수 있는 인터페이스입니다. 이는 비동기 연산의 결과를 얻는 데 필수적입니다.

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의 결합체

FutureTaskRunnableFuture를 동시에 구현한 클래스로, 다음과 같은 특징을 가집니다:

  • 직접 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);

또한 ExecutorServicesubmit(FutureTask) 형태로 제출 가능하여, 기존의 Runnable 기반 코드와도 호환됩니다.

권장 사항 및 주의사항

  • 스레드 풀 사용 후 반드시 shutdown() 호출하여 리소스 해제
  • 작업 결과를 기다릴 때 get()은 블로킹이므로 적절한 타임아웃 설정 권장
  • 실제 운영 환경에서는 Executors의 정적 팩토리보다 ThreadPoolExecutor 직접 구성이 더 안정적

태그: java 스레드 풀 ExecutorService Callable Future

7월 13일 21:34에 게시됨