webFlux

[webFlux] Future, CompletableFuture (1)

ssddo 2025. 4. 26. 18:36

java가 버전 8이 되었을 때, 많은 변화가 있었습니다.

그 중에 하나는 바로  Lamda, Method reference 등 새로운 기능을 지원했다는 것입니다

 

이와 관련하여 이번 포스팅에서는 Future, CompletableFuture 에 대해서 다뤄보도록 하겠습니다. 

 

 

 

 


1️⃣  CompletableFuture

 

CompletableFuture 은 CompletionStage 인터페이스와 Future 인터페이스 모두 구현할 수 있습니다. 

Future 인터페이스는 비동기 결과 조회를 할 때, CompletionStage 인터페이스는 비동기 체이닝 작업을 할 때 주로 사용합니다. 

Future Java5 추가된 인터페이스 (비동기 결과 조회용)
CompletionStage Java8 추가된 인터페이스 (비동기 작업 체이닝용)
CompletableFuture Java8 추가된 클래스 (Future,  CompletionStage 모두 구현 가능)

 

그리고 CompletableFuture 은 비동기 작업 완료 후 람다를 넘기거나 메소드 레퍼런스를 넘겨서 다음 작업을 연결합니다.

다음으로 Method reference 에 대해서 알아보겠습니다. 

 

 


2️⃣  Method reference

 

Method reference 는 :: 연산자를 이용해서 함수에 대한 참조를 간결하게 표현할 수 있습니다.

그리고 Method reference는 크게 네가지로 제공이 됩니다.

타입 예시 설명
constructor method reference Person::new Person 객체 생성자 참조
method reference target::compareTo target 객체의 compareTo 메소드 참조
instance method reference Person::getName Person 인스턴스의 getName 메소드 참조
static method reference MethodReferenceExample::print MethodReferenceExample 클래스의 print 메소드 참조

 

간단한 예시코드는 아래와 같습니다. 

public class MethodReferenceExample {
    public static void main(String[] args) {
        var target = new Person("f");
        Consumer<String> staticPrint = MethodReferenceExample::print;

        Stream.of("a", "b", "g", "h")
              .map(Person::new)              // constructor reference
              .filter(target::compareTo)      // method reference
              .map(Person::getName)           // instance method reference
              .forEach(staticPrint);          // static method reference
    }

    public static void print(String name) {
        System.out.println(name);
    }
}

 


메소드 레퍼런스는 아래의 예시코드처럼 조금 더 짧게 사용할 수 있습니다. 

// 람다 사용
CompletableFuture.supplyAsync(() -> "Hello")
    .thenApply(result -> result.toUpperCase());

// 메소드 레퍼런스 사용
CompletableFuture.supplyAsync(() -> "Hello")
    .thenApply(String::toUpperCase);

 

다음으로 CompletableFuture 클래스가 구현할 수 있는 Future 인터페이스에 대해 알아보겠습니다.

 

 

 


3️⃣  Future

📒 Future 란?

  • java 5에서 추가된 인터페이스 입니다. 
  • Future<V> 는 비동기 작업의 결과를 표현하는 인터페이스 입니다.
public interface Future<V> {
    boolean cancel(boolean mayInterruptIfRunning);
    boolean isCancelled();
    boolean isDone();
    V get() throws InterruptedException, ExecutionException;
    V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException;
}

 

📒  Future  메소드

Future 의 주요 메소드를  표로 먼저 정리해보겠습니다. 

메소드 설명
cancel(boolean mayInterruptIfRunning) 현재 진행 중인 작업을 취소 시도합니다.
isCancelled() 작업이 취소되었는지 확인합니다.
isDone() 작업이 완료되었는지 확인합니다.
get() 작업이 끝날 때까지 기다렸다가 결과를 반환합니다.
get(long timeout, TimeUnit unit) 지정된 시간 동안 기다렸다가 결과를 반환하거나, 시간이 초과되면 예외를 던집니다.

 

조금 더 자세히 알아보겠습니다. 

 

🟡  FutureHelper

  • getFuture : 새로운 쓰레드를 생성하여 1을 반환합니다.
  • getFutureCompleteAfter1S : 새로운 쓰레드를 생성하고 1초 대기 후 1을 반환합니다.

 

🟡  Future:get()

  • 결과를 구할 때까지 쓰레드가 계속 block 상태가 됩니다.
  • future 에서 무한 루프나 오랜시간이 걸린다면 쓰레드가 blocking 상태를 유지합니다. 

 

🟡  Future:get(long timeout,TimeUnit unit)

  • 결과를 구할 때까지 timeout동안 쓰레드가 block 됩니다.
  • timeout이 넘어가도 응답이 반환되지 않으면 TimeoutException 발생합니다.

 

🟡  Future:cancel(boolean mayinterrupIfRunnuing)

  • future의 작업 실행을 취소합니다.
  • 취소할 수 없는 상황이라면 false를 반환합니다.
  • mayInterrupIfRunning가 false라면 시작하지 않은 작업에 대해서만 취소합니다.

Future는 인터페이스이기 때문에 실제 Future 객체를 만들기 위해서는 비동기 작업을 실행하는 주체가 필요하다는 것을 알 수 있었습니다.
그리고 이 비동기 작업을 실행해주는 대표적인 실행자(executor)로는 ExecutorService가 있습니다.
다음으로는 ExecutorService에 대해서 알아보겠습니다.

 

 

 


4️⃣  ExecutorService

 

📒 ExecutorService 란?

  • 스레드 풀을 이용하여 비동기 작업을 실행 및 관리하는 인터페이스입니다.
  • 별도의 스레드를 생성하고 관리하지 않아도 되므로, 코드를 간결하게 유지할 수 있습니다.
  • 스레드 풀을 이용하여 자원을 효율적으로 관리할 수 있습니다.

 

📒 Executors를 이용한 ExecutorService 생성 방법

메소드 설명
newSingleThreadExecutor() 단일 스레드로 구성된 스레드 풀을 생성합니다.
항상 하나의 작업만 동시에 실행됩니다.
newFixedThreadPool(int n) 고정된 크기의 스레드 풀을 생성합니다.
스레드 수는 n개로, 동시에 n개의 작업을 병렬로 처리할 수 있습니다.
newCachedThreadPool() 필요한 경우 새로운 스레드를 생성하고, 기존 스레드를 재사용합니다.
사용하지 않는 스레드는 일정 시간이 지나면 제거됩니다.
newScheduledThreadPool(int corePoolSize) 주기적 또는 지연된 작업을 실행할 수 있는
고정 크기의 스케줄링 지원 스레드 풀을 생성합니다.
newWorkStealingPool() Work Stealing 알고리즘을 사용하는 ForkJoinPool 기반 스레드 풀을 생성합니다.
작업 분배와 병렬 처리가 효율적으로 이루어집니다.
(CPU 코어 수 기반으로 스레드 수 설정)

 

🧹 요약

 

  • Executors 클래스에서 다양한 스레드 풀 전략을 제공하여 상황에 맞게 쉽게 사용할 수 있습니다. 
  • 스케줄링 작업이나 대규모 병렬 작업에서는 newScheduledThreadPool, newWorkStealingPool이 유용하게 사용됩니다. 

 

 

그렇다면 Future 가 항상 최선의 선택일까요?

어째서 java8에 CompletableFuture가 나오게 된 것일까요?

 

다음으로는 Future의 한계점과 그것으로 인해 CompletableFuture가 나오게 된 배경에 대해 알아보겠습니다.

 

 

 


5️⃣  Future 한계와 CompletableFuture 의 등장

 

📒 Future인터페이스의 한계

 

🟡  1.  외부에서 작업 제어가 어렵다

  • Future 객체는 작업을 cancel()로 취소할 수는 있지만,
    작업 자체를 외부에서 직접 수정하거나 세밀하게 제어하는 것은 불가능합니다.
  • 즉, 한번 실행을 시작한 작업은 중간에 내용을 바꿀 수 없고,
    강제로 멈추거나 변경하는 기능이 매우 제한적입니다.

🟡   2.  비동기 처리가 어렵다 (get()은 블로킹)

  • Future의 get() 메소드는 결과가 준비될 때까지 현재 스레드를 블로킹합니다.
  • 이는 '비동기' 라고 부르기는 하지만,
    사실상 결과를 얻을 때 동기적으로 기다려야 하기 때문에 완전한 비동기 처리라고 보기 어렵습니다.
  • 특히, 결과가 오래 걸리는 작업이라면 get() 호출 시 오랜 시간 스레드가 멈춰 있어야 합니다.

🟡   3.  작업 상태를 세밀하게 확인하기 어렵다

  • Future는 isDone()이나 isCancelled()로 작업이 끝났는지 정도만 알 수 있습니다.
  • 하지만 정상 완료인지, 에러가 발생했는지, 어떤 예외가 났는지를 구체적으로 구분하기 어렵습니다.
  • get()을 호출해봐야 예외가 발생했는지 알 수 있는데,
    이 또한 이미 결과를 요청하는 행위이기 때문에 '상태만 별도로 확인' 하는 것은 불편합니다.

 

[ Future 인터페이스의 한계 요약 ]
- cancel을 제외하고 외부에서 future를 컨트롤할 수 없다
- 반환된 결과를 get()해서 접근하기 때문에 비동기 처리가 어렵다
- 상태구분이 어렵다. (완료되거나 에러가 발생했는지 구분하기가 어렵다)

 

 

🧹 요약

Future는 비동기 프로그래밍의 기초를 제공했지만, 외부 제어의 한계와 실행자(Executor)가 필수인 제약 때문에,

Java 8에서는 실행자 없이도 동작 가능한 CompletableFuture가 새롭게 도입되었습니다.

 

분량조절 실패로 2편에서 CompletableFuture 를 정리해보도록 하겠습니다.