ExecutorService의 이해

2025. 2. 19. 13:53·Java

ExecutorService와 Future, CompletableFuture의 각각의 특징과 기능을 학습후 정리하는 글입니다.

 

ExecutorService는 Java의 java.util.concurrent 패키지에 포함된 인터페이스로, 비동기 작업의 실행을 관리하는 프레임워크를 제공합니다. 이를 통해 직접 스레드를 생성하고 관리하는 복잡성을 줄일 수 있습니다.

 

주요 특징

 

  • 스레드 풀 관리: ExecutorService는 내부적으로 스레드 풀을 사용하여 작업을 효율적으로 관리합니다. 이를 통해 스레드 생성과 종료에 따른 오버헤드를 줄이고 시스템 자원을 효율적으로 활용할 수 있습니다.
  • 작업 제출 및 관리: submit() 메서드를 통해 Callable 또는 Runnable 작업을 제출할 수 있으며, 제출된 작업의 결과는 Future 객체를 통해 추적할 수 있습니다.
  • 종료 관리: shutdown() 또는 shutdownNow() 메서드를 사용하여 ExecutorService의 동작을 제어하고, 새로운 작업의 수락을 중지하거나 현재 실행 중인 작업을 즉시 중단시킬 수 있습니다.

 

사용 예시

public class ExecutorServiceExample {
    public static void main(String[] args) {
        ExecutorService executor = Executors.newFixedThreadPool(3);

        Future<Integer> future = executor.submit(() -> {
            // 긴 작업 수행
            TimeUnit.SECONDS.sleep(2);
            return new Random().nextInt(10);
        });

        try {
            Integer result = future.get();
            System.out.println("결과: " + result);
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        } finally {
            executor.shutdown();
        }
    }
}

 

위 예제에서 ExecutorService는 고정된 스레드 풀을 생성하고, Callable 작업을 제출하여 결과를 Future 객체로 받아옵니다. get() 메서드를 호출하면 작업이 완료될 때까지 현재 스레드(호출 스레드)는 블록됩니다.

 

CompletableFuture

CompletableFuture는 Java 8에서 도입된 클래스이며, 비동기 작업을 처리하고 조합하는 데 사용됩니다.

 

주요 특징

  • 비동기 작업 처리: supplyAsync()와 같은 메서드를 통해 비동기 작업을 쉽게 생성할 수 있습니다.
  • 작업 조합 및 체이닝: thenApply(), thenAccept() 등의 메서드를 사용하여 비동기 작업을 순차적으로 연결하거나 조합할 수 있습니다.
  • 예외 처리: exceptionally() 메서드를 통해 비동기 작업 중 발생한 예외를 처리할 수 있습니다.

 

사용 예시

import java.util.concurrent.*;

public class CompletableFutureExample {
    public static void main(String[] args) {
        CompletableFuture.supplyAsync(() -> {
            // 긴 작업 수행
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                throw new IllegalStateException(e);
            }
            return new Random().nextInt(10);
        }).thenApply(result -> {
            // 결과 가공
            return "결과: " + result;
        }).thenAccept(System.out::println)
          .exceptionally(ex -> {
              System.out.println("예외 발생: " + ex.getMessage());
              return null;
          });

        // 메인 스레드가 종료되지 않도록 대기
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

위 예제에서 CompletableFuture는 비동기적으로 작업을 수행하고, 결과를 가공하여 출력합니다. 또한, 예외 발생 시 이를 처리하는 로직도 포함되어 있습니다.

 

 

ExecutorService와 CompletableFuture의 비교

  • 작업 제출 방식: ExecutorService는 주로 Runnable 또는 Callable을 사용하여 작업을 제출하며, CompletableFuture는 함수형 인터페이스(Supplier 등)를 통해 작업을 정의합니다.
  • 결과 처리: ExecutorService는 Future를 반환하여 결과를 얻기 위해 블로킹 호출이 필요하지만, CompletableFuture는 비동기적으로 콜백을 설정하여 결과를 처리할 수 있습니다.
  • 작업 조합: CompletableFuture는 여러 비동기 작업을 조합하거나 체이닝하는 데 더 유용하며, ExecutorService는 이러한 기능을 직접적으로 제공하지 않습니다.
특징 ExecutorService CompletableFuture
작업 제출 방식 submit(Callable) supplyAsync(Supplier)
결과 처리 Future.get() (블로킹) thenApply() (비동기)
작업 조합 불가능 thenCompose() 지원
예외 처리 try-catch 필요 exceptionally() 지원

-> CompletableFuture는 논블로킹 방식으로 비동기 작업을 더 쉽게 체이닝할 수 있음

 

 

추가) Java 21의 가상 스레드와의 통합

Java 21에서는 가상 스레드(Virtual Threads)가 도입되어, 경량화된 스레드를 생성하고 관리할 수 있습니다. 이로 인해 수많은 동시 작업을 효율적으로 처리할 수 있으며, ExecutorService와 CompletableFuture 모두 가상 스레드와 통합하여 사용할 수 있습니다.

 

 

가상 스레드 사용 예시

import java.util.concurrent.*;

public class VirtualThreadExample {
    public static void main(String[] args) {
        try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
            Future<Integer> future = executor.submit(() -> {
                // 긴 작업 수행
                TimeUnit.SECONDS.sleep(2);
                return new Random().nextInt(10);
            });

            try {
                Integer result = future.get();
                System.out.println("결과: " + result);
            } catch (InterruptedException | ExecutionException e) {
                e.printStackTrace();
            }
        } // executor는 자동으로 종료됩니다.
    }
}

위 예제에서 newVirtualThreadPerTaskExecutor()를 사용하여 각 작업마다 새로운 가상 스레드를 생성하는 Executor를 생성합니다. 이를 통해 수많은 동시 작업을 효율적으로 처리할 수 있습니다.

 

 

'Java' 카테고리의 다른 글

Thread Local 의 동작원리  (0) 2025.02.07
Spring Boot 엑셀 업로드 기능 개발기  (1) 2025.02.04
'Java' 카테고리의 다른 글
  • Thread Local 의 동작원리
  • Spring Boot 엑셀 업로드 기능 개발기
JoshDev
JoshDev
    • 분류 전체보기 (24)
      • Java (3)
      • Spring (9)
      • Test Code (2)
      • WIL (6)
      • Vue.js (2)
      • WEB (0)
      • DB (1)
        • MySQL (1)
  • 인기 글

  • hELLO· Designed By정상우.v4.10.4
JoshDev
ExecutorService의 이해
상단으로

티스토리툴바