SOFTMOA TECHNOLOGY

Java 동시성 프로그래밍 - CompletableFuture

소프트모아가 정리한 기술 기록입니다.

Contents
작성일 2025. 11. 30.

Java 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는 메모리 누수 가능

Share this

Java

Have a questions?

견적 및 기술문의

mobile : 010-7931-4813

Contact Form