Java

[Java] CompletableFuture 클래스 사용 방법

cob 2023. 6. 2. 16:41

 

CompletableFuture 클래스란?
Java 8부터 도입된 클래스로, 비동기적인 작업을 처리하고 결과를 다루는 데 사용된다.

 

 

 


1. 비동기 실행 방법

1-1) runAsync

비동기로 작업을 실행하고 결과를 반환하지 않고, Runnable 인터페이스를 구현한 람다 표현식으로 정의된다.
CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
    // 비동기로 실행될 작업
});

 

1-2) supplyAsync

비동기로 작업을 실행하고 결과를 제공하는 CompletableFuture를 반환한다. Supplier 인터페이스를 구현한 람다 표현식으로 정의된다.
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
    // 비동기로 실행될 작업
    return "작업 결과";
});

 

1-3) submit

ExecutorService를 사용하여 비동기 작업을 실행하고, Future객체를 반환한다. Callable인터페이스를 구현한 람다 표현식으로 정의된다.
ExecutorService executor = Executors.newFixedThreadPool(10);
Future<String> future = executor.submit(() -> {
    // 비동기로 실행될 작업
    return "작업 결과";
});

 

1-4) invokeAll

ExecutorService를 사용하여 여러 개의 비동기 작업을 동시에 실행하고, List<Future> 객체를 반환한다. Callable 인터페이스를 구현한 람다 표현식으로 정의된다.
ExecutorService executor = Executors.newFixedThreadPool(10);
List<Callable<String>> tasks = Arrays.asList(
    () -> {
        // 비동기로 실행될 작업 1
        return "작업 1 결과";
    },
    () -> {
        // 비동기로 실행될 작업 2
        return "작업 2 결과";
    }
);
List<Future<String>> futures = executor.invokeAll(tasks);

 

 

 


2. 비동기 실행 결과 콜백 방법

CompletableFuture를 체인으로 연결하고, 비동기 작업의 결과를 처리하거나 다음 작업을 정의할 수 있다. 콜백 메서드들은 비동기 작업의 완료 시점에 실행되며, 이전 작업의 결과를 입력으로 받아 처리하거나 예외 처리한다.

 

2-1) thenApply

이전 CompletableFuture의 결과를 입력으로 받아 작업을 처리하고, 결과를 반환하는 CompletableFuture를 생성한다. Function 인터페이스를 구현한 람다 표현식으로 정의된다.
CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
    // 비동기로 실행될 작업
    return 10;
}).thenApply(result -> result * 2);

 

2-2) thenAccept

이전 CompletableFuture의 결과를 입력으로 받아 작업을 처리하고 결과를 반환하지 않는다. Consumer 인터페이스를 구현한 람다 표현식으로 정의된다.
CompletableFuture<Void> future = CompletableFuture.supplyAsync(() -> {
    // 비동기로 실행될 작업
    return "작업 결과";
}).thenAccept(result -> {
    // 결과를 처리하는 작업
    System.out.println("결과: " + result);
});

 

 

2-3) thenRun

이전 CompletableFuture의 결과에 상관없이 실행될 작업을 정의한다. 작업은 Runnable 인터페이스를 구현한 람다 표현식으로 정의되고, 결과를 반환하지 않는다.
CompletableFuture<Void> future = CompletableFuture.supplyAsync(() -> {
    // 비동기로 실행될 작업
    return "작업 결과";
}).thenRun(() -> {
    // 이전 작업과 상관없이 실행될 작업
    System.out.println("작업이 완료되었습니다.");
});

 

 

2-4) exceptionally

CompletableFuture가 예외로 완료된 경우, 예외를 처리하고 대체 결과를 반환한다. Function 인터페이스를 구현한 람다 표현식으로 정의된다.
CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
    // 비동기로 실행될 작업
    throw new RuntimeException("에러 발생");
}).exceptionally(ex -> {
    // 예외를 처리하고 대체 결과 반환
    System.out.println("에러 발생: " + ex.getMessage());
    return -1;
});

 

 

 


3. 조합 처리 방법

3-1) allOf

여러 개의 CompletableFuture들이 모두 완료될 때까지 기다리고, 모든 CompletableFuture가 완료되면
CompletableFuture<Void>를 반환한다. 결과값은 null 이다.
CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> "Result 1");
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> "Result 2");

CompletableFuture<Void> allFutures = CompletableFuture.allOf(future1, future2);
allFutures.thenRun(() -> {
    System.out.println("All futures completed.");
});

 

 

3-2) anyOf

여러 개의 CompletableFuture들 중 가장 빨리 완료되는 하나의 CompletableFuture를 반환한다. 어떤 CompletableFuture가 먼저 완료되더라도 첫 번째 완료된 결과를 반환한다.
CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> "Result 1");
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> "Result 2");

CompletableFuture<Object> anyFuture = CompletableFuture.anyOf(future1, future2);
anyFuture.thenAccept(result -> {
    System.out.println("First completed future result: " + result);
});

 

3-3) thenCompose

두 작업이 이어서 실행하도록 조합하며, 앞선 작업의 결과를 받아서 사용할 수 있다. Function 인터페이스를 구현한 람다 표현식으로 정의된다.
CompletableFuture<Integer> future1 = CompletableFuture.supplyAsync(() -> 10);

CompletableFuture<CompletableFuture<String>> futureCompose = future1.thenApply(result -> {
    CompletableFuture<String> nestedFuture = CompletableFuture.supplyAsync(() -> "Result: " + result);
    return nestedFuture;
});

 

 

3-4) thenCombine

두 작업을 독립적으로 실행하고, 둘 다 완료되었을 때 콜백을 실행하며 함수의 실행 결과를 포함하는 새로운 CompletableFuture를 반환한다.
CompletableFuture<Integer> future1 = CompletableFuture.supplyAsync(() -> 10);
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> "Hello");

CompletableFuture<String> combinedFuture = future1.thenCombine(future2, (result1, result2) -> result1 + " " + result2);
// 10 Hello

 

 


4. 활용

4-1) 비동기 API 호출 응답 처리

CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
    // 외부 API 호출 또는 데이터베이스 쿼리 등의 I/O 작업 수행
    String result = someExternalService.callApi();
    return result;
});

future.thenAccept(result -> {
    // 비동기 작업이 완료된 후 결과 처리
    System.out.println("API 응답 결과: " + result);
});
  • supplyAsync : 비동기 작업을 실행하고, 외부 API 호출 또는 데이터베이스 쿼리 등의 I/O 작업을 수행한다.
  • thenAccept : 작업이 완료된 후에 결과를 처리하는 콜백을 등록한다.

 

4-2) 병렬 작업 처리 

CompletableFuture<Integer> future1 = CompletableFuture.supplyAsync(() -> {
    // 작업 1
    return 10;
});

CompletableFuture<Integer> future2 = CompletableFuture.supplyAsync(() -> {
    // 작업 2
    return 20;
});

CompletableFuture<Integer> combinedFuture = future1.thenCombine(future2, (result1, result2) -> {
    // 두 작업의 결과를 조합하여 처리
    return result1 + result2;
});

combinedFuture.thenAccept(result -> {
    // 조합된 결과 처리
    System.out.println("두 작업의 합: " + result);
});
  • supplyAsync : 각각의 작업을 병렬로 실행한다.
  • thenCombine : 두 작업의 결과를 조합하여 새로운 CompletableFuture를 생성한다.
  • thenAccept : 조합된 결과를 처리하는 콜백을 등록한다.

 

 

반응형