JAVA

CompletableFuture (비동기 처리)

도원좀비 2025. 3. 12. 20:30

1️⃣ CompletableFuture란?

  1. 비동기 작업(Async Task)을 쉽게 관리할 수 있는 클래스
  2. 콜백(Callback) 기반으로 동작하며, 작업 완료 후 후속 처리를 간편하게 정의 가능
  3. Future의 단점을 해결하여, 결과를 기다리지 않고도 다음 작업을 수행할 수 있다.

2️⃣ Future vs CompletableFuture 차이

기존 Future의 문제점

기존 Future 객체는 비동기 작업을 실행할 수 있지만 제한점이 많음

더보기
import java.util.concurrent.*;

public class FutureExample {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService executor = Executors.newFixedThreadPool(2);

        Future<String> future = executor.submit(() -> {
            Thread.sleep(2000); // 2초 대기
            return "작업 완료!";
        });

        System.out.println("Future 작업 실행 중...");

        // 🔹 `get()`을 호출하면 결과를 받을 때까지 블로킹됨 (문제점)
        String result = future.get(); // 결과를 기다림
        System.out.println("결과: " + result);

        executor.shutdown();
    }
}

Future의 문제점

  1. get()을 호출하면 결과가 나올 때까지 기다려야 함 (Blocking)
  2. 작업 완료 후 후속 처리(thenApply 같은 체이닝 기능)가 없음
  3. 여러 개의 Future를 조합하는 것이 불편함

3️⃣ CompletableFuture 기본 사용법

CompletableFuture를 사용하면 콜백 기반으로 작업을 연결(Chaining)할 수 있어 더욱 유연하다

 

기본적인 비동기 실행 (runAsync vs supplyAsync)

더보기
import java.util.concurrent.CompletableFuture;

public class CompletableFutureExample {
    public static void main(String[] args) {
        //  반환 값이 없는 비동기 작업
        CompletableFuture.runAsync(() -> {
            System.out.println("비동기 작업 실행 중... " + Thread.currentThread().getName());
        });

        //  반환 값이 있는 비동기 작업
        CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
            return "비동기 작업 결과!";
        });

        System.out.println("메인 스레드는 계속 실행됨!");

        // 결과를 기다림 (하지만 `Future`보다 훨씬 더 유연하게 사용 가능)
        System.out.println("결과: " + future.join()); // `join()`을 사용하면 블로킹 없이 결과를 가져올 수 있음
    }
}

 

✅ runAsync() → 반환 값이 필요 없을 때 사용
✅ supplyAsync() → 비동기 작업의 결과값을 받을 때 사용


4️⃣ CompletableFuture 체이닝 (Chaining)

비동기 작업이 끝난 후 다음 작업을 실행하는 방법

 

thenApply() (결과를 변환)

더보기
import java.util.concurrent.CompletableFuture;

public class CompletableFutureChaining {
    public static void main(String[] args) {
        CompletableFuture.supplyAsync(() -> "Hello")
                .thenApply(result -> result + " World")  // 결과 변환
                .thenApply(result -> result.toUpperCase()) // 대문자로 변환
                .thenAccept(System.out::println); // 최종 결과 출력
    }
}

✅ thenApply() → 이전 작업의 결과를 받아서 변환한 후 반환


5️⃣ thenCompose() vs thenCombine() (비동기 작업 조합)

여러 개의 CompletableFuture를 조합해서 실행하는 방법

 

thenCompose() - 이전 결과를 기반으로 새로운 비동기 작업 실행

더보기
import java.util.concurrent.CompletableFuture;

public class CompletableFutureThenCompose {
    public static void main(String[] args) {
        CompletableFuture.supplyAsync(() -> "User ID: 123")
                .thenCompose(id -> getUserDetails(id)) // 새로운 비동기 작업 실행
                .thenAccept(System.out::println);
    }

    // 🔹 유저 정보를 가져오는 비동기 메서드
    public static CompletableFuture<String> getUserDetails(String userId) {
        return CompletableFuture.supplyAsync(() -> userId + " (홍길동, 29세)");
    }
}

✅ thenCompose() → 이전 결과를 이용하여 새로운 CompletableFuture 실행

 

thenCombine() - 두 개의 비동기 작업을 병렬로 실행한 후 결과 합치기

더보기
import java.util.concurrent.CompletableFuture;

public class CompletableFutureThenCombine {
    public static void main(String[] args) {
        CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> "Hello");
        CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> "World");

        // 두 개의 비동기 작업을 병렬로 실행한 후 결과를 조합
        future1.thenCombine(future2, (result1, result2) -> result1 + " " + result2)
               .thenAccept(System.out::println);
    }
}

6️⃣ 예외 처리 (exceptionally, handle)

비동기 작업에서 예외가 발생했을 때 처리하는 방법

 

exceptionally() - 예외 발생 시 기본값 반환

더보기
import java.util.concurrent.CompletableFuture;

public class CompletableFutureException {
    public static void main(String[] args) {
        CompletableFuture.supplyAsync(() -> {
            if (true) throw new RuntimeException("에러 발생!");
            return "정상 실행";
        }).exceptionally(ex -> {
            System.out.println("예외 처리: " + ex.getMessage());
            return "기본값";
        }).thenAccept(System.out::println);
    }
}

 

handle() - 정상 / 예외 모두 처리 가능

더보기
import java.util.concurrent.CompletableFuture;

public class CompletableFutureHandle {
    public static void main(String[] args) {
        CompletableFuture.supplyAsync(() -> {
            if (true) throw new RuntimeException("에러 발생!");
            return "정상 실행";
        }).handle((result, ex) -> {
            if (ex != null) {
                System.out.println("예외 처리: " + ex.getMessage());
                return "기본값";
            }
            return result;
        }).thenAccept(System.out::println);
    }
}

handle() → 정상적인 결과와 예외를 모두 처리할 수 있음


CompletableFuture 정리

기능 메서드
비동기 작업 실행 runAsync(), supplyAsync()
후속 작업 추가 thenApply(), thenAccept()
비동기 작업 조합 thenCompose(), thenCombine()
예외 처리 exceptionally(), handle()

'JAVA' 카테고리의 다른 글

Java Map(맵)  (2) 2025.03.12
가상 쓰레드 (Virtual Threads)  (0) 2025.03.12
Executor 프레임워크  (0) 2025.03.12
쓰레드 동기화 (Synchronization)  (3) 2025.03.11
쓰레드(Thread)  (0) 2025.03.11