Contents
see ListJava CompletableFuture란?
CompletableFuture는 Java 8에서 도입된 비동기 프로그래밍 도구입니다. Future의 한계를 극복하고, 비동기 작업의 조합, 예외 처리, 콜백 등을 지원합니다.
언제 사용하나요?
- 여러 API를 동시에 호출하고 결과를 조합할 때
- 시간이 오래 걸리는 작업을 비동기로 처리할 때
- 복잡한 비동기 워크플로우 구현
- 타임아웃과 폴백 처리가 필요할 때
기본 사용법
// 비동기 작업 시작
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
// 오래 걸리는 작업
return fetchDataFromAPI();
});
// 결과 대기 (블로킹)
String result = future.get();
// 타임아웃과 함께 대기
String result = future.get(5, TimeUnit.SECONDS);
// 완료 시 콜백 (논블로킹)
future.thenAccept(result -> System.out.println(result));비동기 작업 체이닝
CompletableFuture.supplyAsync(() -> getUserId())
.thenApply(userId -> fetchUser(userId)) // 변환
.thenApply(user -> user.getEmail()) // 또 변환
.thenAccept(email -> sendEmail(email)) // 소비
.thenRun(() -> log.info("완료")); // 후처리
// thenApply vs thenCompose
// thenApply: T -> U (값 변환)
// thenCompose: T -> CompletableFuture<U> (Future 평탄화)여러 작업 조합
// 두 작업 모두 완료 후 조합
CompletableFuture<User> userFuture = fetchUserAsync(id);
CompletableFuture<List<Order>> ordersFuture = fetchOrdersAsync(id);
CompletableFuture<UserWithOrders> combined =
userFuture.thenCombine(ordersFuture,
(user, orders) -> new UserWithOrders(user, orders));
// 여러 작업 모두 완료 대기
CompletableFuture<Void> allOf = CompletableFuture.allOf(
future1, future2, future3
);
allOf.join(); // 모두 완료될 때까지 대기
// 먼저 완료되는 작업 사용
CompletableFuture<String> fastest = CompletableFuture.anyOf(
callApiA(), callApiB(), callApiC()
).thenApply(obj -> (String) obj);예외 처리
CompletableFuture.supplyAsync(() -> riskyOperation())
.exceptionally(ex -> {
log.error("오류 발생", ex);
return "기본값"; // 대체값 반환
});
// handle: 성공/실패 모두 처리
CompletableFuture.supplyAsync(() -> riskyOperation())
.handle((result, ex) -> {
if (ex != null) {
return "에러: " + ex.getMessage();
}
return "성공: " + result;
});
// whenComplete: 결과 변경 없이 부수효과만
.whenComplete((result, ex) -> {
if (ex != null) {
log.error("실패", ex);
} else {
log.info("성공: {}", result);
}
});실전 예시: 여러 API 동시 호출
@Service
public class DashboardService {
public DashboardData getDashboard(Long userId) {
// 세 API를 동시에 호출
CompletableFuture<UserInfo> userFuture =
CompletableFuture.supplyAsync(() -> userApi.getInfo(userId));
CompletableFuture<List<Order>> ordersFuture =
CompletableFuture.supplyAsync(() -> orderApi.getRecent(userId));
CompletableFuture<Integer> pointsFuture =
CompletableFuture.supplyAsync(() -> pointApi.getBalance(userId));
// 모든 결과 조합
return CompletableFuture.allOf(userFuture, ordersFuture, pointsFuture)
.thenApply(v -> new DashboardData(
userFuture.join(),
ordersFuture.join(),
pointsFuture.join()
))
.orTimeout(5, TimeUnit.SECONDS)
.exceptionally(ex -> DashboardData.empty())
.join();
}
}커스텀 Executor 사용
ExecutorService executor = Executors.newFixedThreadPool(10);
CompletableFuture.supplyAsync(() -> heavyTask(), executor)
.thenApplyAsync(result -> process(result), executor);
// Spring에서는 @Async와 함께 사용
@Async("taskExecutor")
public CompletableFuture<String> asyncMethod() {
return CompletableFuture.completedFuture(result);
}주의사항
- join()은 예외를 CompletionException으로 래핑
- get()은 checked exception 발생 (InterruptedException)
- ForkJoinPool.commonPool() 사용 시 스레드 부족 주의
- 완료되지 않은 Future는 메모리 누수 가능