ExecutorService — 종료 관리와 Future 제공 Executor

ExecutorService — 종료 관리와 Future 제공 Executor

ExecutorService종료(termination)를 관리하는 메서드와 하나 이상의 비동기 태스크 진행을 추적하는 Future 를 생성하는 메서드를 제공하는 Executor예요. 자바에서 스레드 풀을 다룰 때 가장 흔히 쓰는 인터페이스예요.

출처: Java API Reference

본문

개념 이해하기

ExecutorService는 종료 관리 메서드와 비동기 태스크의 진행을 추적하는 Future를 생성하는 메서드를 제공하는 Executor예요. 종료되면 새 태스크를 거부해요.

public interface ExecutorService
extends Executor, AutoCloseable
  • shutdown(): 이미 제출된 태스크는 실행하게 하되 새 태스크는 받지 않는 질서 있는 정리를 시작해요.
  • shutdownNow(): 대기 태스크는 시작하지 못하게 하고, 실행 중인 태스크를 중지하려 시도해요.
  • 완료 시: 실행 중 태스크도, 실행 대기 태스크도 없고 새 태스크 제출도 불가능해요. 사용하지 않는 ExecutorService는 자원 회수를 위해 종료해야 해요.
  • submitExecutor.execute(Runnable)를 확장해 실행 취소/완료 대기에 쓰는 Future를 만들어 반환해요.
  • invokeAny/invokeAll은 태스크 컬렉션을 실행하고 하나 또는 모두가 완료될 때까지 기다리는 일괄 실행을 제공해요.
  • Executors 클래스가 이 패키지의 executor 서비스 팩토리 메서드를 제공해요.

활용 예 — 네트워크 서비스

class NetworkService implements Runnable {
    private final ServerSocket serverSocket;
    private final ExecutorService pool;
    public NetworkService(int port, int poolSize) throws IOException {
        serverSocket = new ServerSocket(port);
        pool = Executors.newFixedThreadPool(poolSize);
    }
    public void run() {
        try {
            for (;;) { pool.execute(new Handler(serverSocket.accept())); }
        } catch (IOException ex) { pool.shutdown(); }
    }
}

try-with-resources로도 열고 닫을 수 있어요 (JDK 19+ close):

try (ExecutorService e = Executors.newWorkStealingPool()) {
    // submit or execute many tasks with e ...
}

활용 예 — 두 단계 종료

void shutdownAndAwaitTermination(ExecutorService pool) {
    pool.shutdown();                       // Disable new tasks from being submitted
    try {
        if (!pool.awaitTermination(60, TimeUnit.SECONDS)) {
            pool.shutdownNow();            // Cancel currently executing tasks
            if (!pool.awaitTermination(60, TimeUnit.SECONDS))
                System.err.println("Pool did not terminate");
        }
    } catch (InterruptedException ex) {
        pool.shutdownNow();                // (Re-)Cancel if current thread also interrupted
        Thread.currentThread().interrupt(); // Preserve interrupt status
    }
}

메모리 일관성 효과: 한 스레드에서 태스크를 제출하기 전의 동작은 그 태스크가 수행하는 동작보다 happen-before이고, 이는 다시 Future.get()으로 결과를 가져오기 전에 happen-before예요.

종료 메서드

void shutdown() — 질서 있는 정리를 시작해요. 이미 종료됐으면 추가 효과가 없어요. 제출된 태스크가 완료되길 기다리지 않아요(awaitTermination 사용).

  • SecurityException — 보안 관리자 존재 시

List<Runnable> shutdownNow() — 실행 중인 태스크를 모두 중지하려 시도하고 대기 태스크 처리를 중단하며, 실행 대기 중이던 태스크 목록을 반환해요. best-effort만 보장해요(보통 Thread.interrupt()로 취소하므로 인터럽트에 응답하지 않는 태스크는 끝나지 않을 수 있어요).

  • SecurityException

boolean isShutdown() — 종료됐으면 true.

boolean isTerminated() — 종료 후 모든 태스크가 완료됐으면 true. shutdown/shutdownNow가 먼저 호출되지 않으면 true가 되지 않아요.

boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException — 종료 요청 후 모든 태스크가 완료되거나, 타임아웃이 발생하거나, 인터럽트될 때까지 블로킹해요. 종료됐으면 true, 타임아웃이면 false.

  • InterruptedException

default void close() — 질서 있는 정리를 시작하고 모든 태스크 완료·executor 종료까지 대기해요. 대기 중 인터럽트되면 shutdownNow()처럼 실행 태스크를 멈추고, 실행 중 태스크가 끝날 때까지 계속 기다려요. (JDK 19+)

submit 메서드

<T> Future<T> submit(Callable<T> task) — 값을 반환하는 태스크를 제출하고 대기 중 결과를 나타내는 Future를 반환해요. result = exec.submit(aCallable).get(); 형태로 블로킹 대기할 수 있어요.

  • RejectedExecutionException, NullPointerException

<T> Future<T> submit(Runnable task, T result)Runnable 태스크를 제출하고 그 태스크를 나타내는 Future를 반환해요. 완료 시 get()result를 반환.

  • RejectedExecutionException, NullPointerException

Future<?> submit(Runnable task)Runnable 태스크를 제출하고 그 태스크를 나타내는 Future를 반환해요. 완료 시 get()null을 반환.

  • RejectedExecutionException, NullPointerException

일괄 실행

<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks) throws InterruptedException — 주어진 태스크를 실행하고 모두 완료될 때 상태·결과를 담은 Future 목록을 반환해요. 반환 목록의 각 요소는 Future.isDone()true예요. 완료된 태스크는 정상 종료이거나 예외를 던져 끝났을 수 있어요. 연산 중 컬렉션이 수정되면 결과는 정의되지 않아요.

  • InterruptedException (대기 중 인터럽트 — 미완료 태스크는 취소), NullPointerException, RejectedExecutionException

<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) throws InterruptedException — 모두 완료되거나 타임아웃이 지날 때 중 먼저 일어나는 때의 상태·결과 Future 목록을 반환해요. 반환 시 완료되지 않은 태스크는 취소돼요.

  • InterruptedException, NullPointerException, RejectedExecutionException

<T> T invokeAny(Collection<? extends Callable<T>> tasks) throws InterruptedException, ExecutionException — 성공적으로 완료된(예외를 던지지 않은) 하나의 태스크 결과를 반환해요. 정상·예외 반환 시 완료되지 않은 태스크는 취소돼요.

  • InterruptedException, NullPointerException, IllegalArgumentException (tasks가 비었을 때), ExecutionException (성공 완료 태스크가 없을 때), RejectedExecutionException

<T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException — 타임아웃 전에 성공 완료된 하나의 태스크 결과를 반환해요.

  • TimeoutException (타임아웃 전 성공 완료 태스크 없음) 등

더 알아보기 (Learn more)