Java동시성과 스레드

ExecutorService, Callable, Future

직접 Thread를 만드는 방식과 thread pool 기반 실행 방식이 어떻게 다르고, Callable/Future를 왜 함께 쓰는지 정리합니다.

마지막 수정 2026년 3월 22일

기본 패턴

java
ExecutorService pool = Executors.newFixedThreadPool(4);

Future<Integer> future = pool.submit(() -> 40 + 2);

int value = future.get();
pool.shutdown();

설명

  • 스레드를 직접 만들면 교육용 예제는 단순해도, 실제 서비스에서는 생성 비용과 종료 시점, 개수 제한을 직접 다뤄야 해서 금방 복잡해집니다. ExecutorService는 이 문제를 thread pool이라는 형태로 관리합니다.
  • Runnable은 반환값이 없고, Callable<V>는 결과를 반환하거나 checked exception을 던질 수 있습니다. 계산 작업이나 I/O 작업 결과를 받아야 할 때 Callable이 자연스럽습니다.
  • submit()은 작업을 큐에 넣고 Future를 돌려줍니다. Future는 나중에 결과를 기다리거나 완료 여부를 확인하거나 취소할 수 있게 해 주는 핸들입니다.
  • 핵심은 "작업 정의", "실행 자원", "결과 회수"를 분리하는 데 있습니다. 이 분리가 되어야 요청량이 늘어날 때 thread 수를 통제하고, timeout이나 cancellation 정책도 붙일 수 있습니다.
  • 실무에선 CompletableFuture나 framework 비동기 모델을 더 많이 보더라도, 그 아래 기초 감각은 대부분 ExecutorServiceFuture 위에서 이해하는 편이 좋습니다.

빠른 정리

요소의미
ExecutorService작업 실행을 관리하는 thread pool
Callable<V>결과를 돌려주는 작업
Future<V>결과, 취소, 완료 여부를 다루는 핸들
submit()작업 제출
shutdown()새 작업을 받지 않고 정리 시작

주의할 점

Future.get()은 결과가 준비될 때까지 현재 thread를 block합니다. 비동기 구조를 만든 뒤에도 무심코 get()을 남발하면 결국 동기 코드처럼 병목이 생길 수 있습니다.

참고 링크

3 sources