F-LAB JAVA · 4주차 · Phase 8 · 고급 비동기
🚀 Phase 8 시작 + ★ 마스터 Unit — 비동기 프로그래밍의 정점
이 Unit을 끝내면 다음을 답할 수 있어야 한다.
CompletableFuture 는 Future 의 블로킹·조합 불가 한계를 극복한 자바 8 의 비동기 도구로, 콜백 체이닝을 통해 논블로킹으로 비동기 작업을 조합·변환·처리한다.
supplyAsync는 결과를 반환하는 비동기 작업 (Supplier),runAsync는 반환값 없는 작업 (Runnable) 을 시작한다.
thenApply는 결과를 변환 (Function),thenAccept는 결과를 소비 (Consumer),thenRun은 결과 무관하게 실행 (Runnable) 하며,thenCompose는 또 다른 CompletableFuture 를 반환하는 작업을 평탄하게 연결 (flatMap),thenCombine은 두 독립 CompletableFuture 의 결과를 합친다.
예외 처리는exceptionally(예외 시 대체값),handle(결과+예외 모두),whenComplete(결과+예외 관찰, 변환 X) 로 하고,allOf(모두 완료),anyOf(하나 완료) 로 여러 작업을 조합한다.
thenApply는 이전 작업 스레드에서 (또는 호출 스레드),thenApplyAsync는 별도 스레드 풀에서 실행되어 스레드 제어가 가능하다.
CompletableFuture = 자동 조립 라인:
Future = 수동 픽업:
- 부품 주문 → 보관증
- 다 됐는지 보러 감 (블로킹)
- 받아서 다음 단계 수동
CompletableFuture = 컨베이어 벨트:
supplyAsync = 부품 생산 시작
thenApply = 다음 공정 (자동 연결)
thenAccept = 포장
thenCompose = 다른 라인과 연결
thenCombine = 두 부품 조립
논블로킹:
- "다 되면 자동으로 다음" (콜백)
- 보러 갈 필요 X
- 벨트가 알아서 흐름
예외 처리:
- exceptionally: 불량 시 대체 부품
- handle: 정상/불량 모두 처리
조합:
- allOf: 모든 라인 완료 대기
- anyOf: 가장 빠른 라인
→ CompletableFuture = 논블로킹 콜백 체이닝 (조립 라인), Future 의 블로킹 극복.
1. CompletableFuture의 정의 (Future 극복)
2. supplyAsync / runAsync
3. thenApply / thenAccept / thenRun
4. thenCompose vs thenCombine
5. 예외 처리 (exceptionally/handle/whenComplete)
6. allOf / anyOf
7. Async 변형 (스레드 제어)
8. 실무 활용
9. 면접 + 자기 점검 + 마스터 50문항
CompletableFuture (Java 8):
Future + CompletionStage 구현.
- 비동기 작업 조합
- 콜백 (논블로킹)
- 예외 처리
Future 한계 극복:
- 블로킹 get → 콜백
- 조합 어려움 → 체이닝
Future 한계:
- get() 블로킹
- 조합 어려움
- 콜백 없음
- 수동 완료 X
CompletableFuture 해결:
- thenApply 등 (콜백)
- thenCompose (조합)
- complete (수동 완료)
// 1. supplyAsync (결과 있음)
CompletableFuture<String> cf = CompletableFuture.supplyAsync(() -> {
return fetchData();
});
// 2. runAsync (결과 없음)
CompletableFuture<Void> cf2 = CompletableFuture.runAsync(() -> {
doWork();
});
// 3. 완료된 Future
CompletableFuture<String> done = CompletableFuture.completedFuture("result");
// 콜백 체이닝 (논블로킹)
CompletableFuture.supplyAsync(() -> fetchData()) // 비동기
.thenApply(data -> process(data)) // 변환
.thenAccept(result -> save(result)) // 소비
.exceptionally(ex -> { // 예외
log.error("실패", ex);
return null;
});
// 블로킹 없음 (완료 시 자동)
CompletionStage 인터페이스:
비동기 계산의 한 단계.
- then* 메서드
- 체이닝
- 조합
CompletableFuture 가 구현
@Service
public class CompletableFutureBasics {
private final ExecutorService executor = Executors.newFixedThreadPool(10);
// 논블로킹 비동기 처리
public CompletableFuture<ShipmentResult> processAsync(Shipment shipment) {
return CompletableFuture
.supplyAsync(() -> calculateFreight(shipment), executor) // 비동기
.thenApply(freight -> createInvoice(shipment, freight)) // 변환
.thenApply(invoice -> new ShipmentResult(invoice)) // 변환
.exceptionally(ex -> { // 예외
log.error("처리 실패: {}", shipment.getId(), ex);
return ShipmentResult.failed();
});
// 블로킹 없이 비동기 파이프라인
}
private BigDecimal calculateFreight(Shipment s) { return s.getWeight(); }
private Invoice createInvoice(Shipment s, BigDecimal f) { return new Invoice(); }
record Invoice() {}
record ShipmentResult(Invoice invoice) {
static ShipmentResult failed() { return new ShipmentResult(null); }
}
}
CompletableFuture가 Future의 한계를 극복하는 점은?
답:
1. 정의:
Future 한계 극복:
생성:
논블로킹:
// supplyAsync — 결과 있음 (Supplier)
CompletableFuture<T> supplyAsync(Supplier<T> supplier);
CompletableFuture<T> supplyAsync(Supplier<T> supplier, Executor executor);
// runAsync — 결과 없음 (Runnable)
CompletableFuture<Void> runAsync(Runnable runnable);
CompletableFuture<Void> runAsync(Runnable runnable, Executor executor);
// supplyAsync — 결과 반환
CompletableFuture<String> cf = CompletableFuture.supplyAsync(() -> {
return fetchData(); // 결과
});
cf.thenApply(data -> process(data)); // 결과 사용
// runAsync — 결과 없음
CompletableFuture<Void> cf = CompletableFuture.runAsync(() -> {
doWork(); // 결과 없음
});
cf.thenRun(() -> log.info("완료")); // 결과 무관
// 기본: ForkJoinPool.commonPool()
CompletableFuture.supplyAsync(() -> task());
// 커스텀 Executor
ExecutorService executor = Executors.newFixedThreadPool(10);
CompletableFuture.supplyAsync(() -> task(), executor);
// 권장: 커스텀 (commonPool 공유 회피)
commonPool 주의:
기본 Executor = ForkJoinPool.commonPool()
- JVM 전역 공유
- 블로킹 작업 시 고갈
- 다른 작업 영향
→ I/O 작업은 커스텀 Executor
@Service
public class SupplyRunAsync {
private final ExecutorService executor = Executors.newFixedThreadPool(10);
// supplyAsync — 결과 (운임 계산)
public CompletableFuture<BigDecimal> calculateAsync(Shipment shipment) {
return CompletableFuture.supplyAsync(
() -> freightCalculator.calculate(shipment),
executor); // 커스텀 Executor
}
// runAsync — 결과 없음 (알림)
public CompletableFuture<Void> notifyAsync(Shipment shipment) {
return CompletableFuture.runAsync(
() -> emailService.send(shipment),
executor);
}
private BigDecimal calculateFreight(Shipment s) { return s.getWeight(); }
}
supplyAsync / runAsync 차이는?
답:
1. supplyAsync:
runAsync:
Executor:
commonPool 주의:
세 콜백:
thenApply (Function):
- 결과 변환 (T → R)
- 결과 반환
thenAccept (Consumer):
- 결과 소비 (T → void)
- 반환 X
thenRun (Runnable):
- 결과 무관 실행
- 입력/반환 X
// thenApply — 변환 (결과 → 새 결과)
CompletableFuture.supplyAsync(() -> "hello")
.thenApply(s -> s.toUpperCase()) // "HELLO"
.thenApply(s -> s.length()); // 5
// 결과를 변환하며 체이닝
// thenAccept — 소비 (결과 사용, 반환 X)
CompletableFuture.supplyAsync(() -> fetchData())
.thenAccept(data -> {
save(data); // 소비
// 반환 없음 (CompletableFuture<Void>)
});
// thenRun — 결과 무관
CompletableFuture.supplyAsync(() -> fetchData())
.thenRun(() -> {
log.info("완료"); // 결과 안 받음
});
| 메서드 | 입력 | 반환 | 함수형 |
|---|---|---|---|
| thenApply | T | R | Function |
| thenAccept | T | void | Consumer |
| thenRun | - | void | Runnable |
@Service
public class ThenMethods {
private final ExecutorService executor = Executors.newFixedThreadPool(10);
public CompletableFuture<Void> processShipment(Shipment shipment) {
return CompletableFuture
.supplyAsync(() -> calculateFreight(shipment), executor) // BigDecimal
.thenApply(freight -> { // 변환
return createInvoice(shipment, freight); // Invoice
})
.thenAccept(invoice -> { // 소비
invoiceRepository.save(invoice);
})
.thenRun(() -> { // 결과 무관
log.info("배송 처리 완료: {}", shipment.getId());
});
}
private BigDecimal calculateFreight(Shipment s) { return s.getWeight(); }
private Invoice createInvoice(Shipment s, BigDecimal f) { return new Invoice(); }
record Invoice() {}
}
thenApply / thenAccept / thenRun 차이는?
답:
1. thenApply:
thenAccept:
thenRun:
선택:
두 조합:
thenCompose (flatMap):
- 또 다른 CF 반환 작업 연결
- 순차 의존 (A → B(A))
thenCombine (zip):
- 두 독립 CF 결과 합침
- 병렬 (A, B → C)
// thenCompose — 순차 의존 (flatMap)
CompletableFuture.supplyAsync(() -> getUserId()) // CF<Long>
.thenCompose(userId ->
CompletableFuture.supplyAsync(() -> getUser(userId))); // CF<User>
// userId → user (의존)
// thenApply 면 CF<CF<User>> (중첩)
// thenCompose 는 평탄 (CF<User>)
// ❌ thenApply (중첩)
CompletableFuture<CompletableFuture<User>> nested =
CompletableFuture.supplyAsync(() -> getUserId())
.thenApply(id -> getUserAsync(id)); // CF<CF<User>>
// ✓ thenCompose (평탄)
CompletableFuture<User> flat =
CompletableFuture.supplyAsync(() -> getUserId())
.thenCompose(id -> getUserAsync(id)); // CF<User>
// thenCombine — 두 독립 작업 합침
CompletableFuture<BigDecimal> freightF =
CompletableFuture.supplyAsync(() -> calculateFreight());
CompletableFuture<BigDecimal> taxF =
CompletableFuture.supplyAsync(() -> calculateTax());
CompletableFuture<BigDecimal> totalF =
freightF.thenCombine(taxF, (freight, tax) -> freight.add(tax));
// 두 작업 병렬 → 결과 합침
thenCompose vs thenCombine:
thenCompose:
- 순차 의존
- A 결과로 B 시작
- flatMap
thenCombine:
- 독립 병렬
- A, B 동시 → 합침
- zip
@Service
public class ComposeCombine {
private final ExecutorService executor = Executors.newFixedThreadPool(10);
// thenCompose — 의존 (예약 → 추적)
public CompletableFuture<TrackingInfo> getTracking(Long bookingId) {
return CompletableFuture
.supplyAsync(() -> findBooking(bookingId), executor) // CF<Booking>
.thenCompose(booking -> // 의존
CompletableFuture.supplyAsync(() ->
trackingApi.fetch(booking.getBlNo()), executor)); // CF<Tracking>
}
// thenCombine — 독립 (운임 + 세금 병렬)
public CompletableFuture<BigDecimal> calculateTotal(Shipment shipment) {
CompletableFuture<BigDecimal> freightF = CompletableFuture
.supplyAsync(() -> calculateFreight(shipment), executor);
CompletableFuture<BigDecimal> taxF = CompletableFuture
.supplyAsync(() -> calculateTax(shipment), executor);
return freightF.thenCombine(taxF, BigDecimal::add); // 병렬 → 합침
}
private Booking findBooking(Long id) { return new Booking(); }
private BigDecimal calculateFreight(Shipment s) { return s.getWeight(); }
private BigDecimal calculateTax(Shipment s) { return BigDecimal.ONE; }
record Booking() { String getBlNo() { return "BL"; } }
record TrackingInfo() {}
}
thenCompose vs thenCombine 차이는?
답:
1. thenCompose:
thenCombine:
thenApply vs Compose:
선택:
예외 처리:
exceptionally (Function):
- 예외 시 대체값
- 예외 → 결과
handle (BiFunction):
- 결과 + 예외 모두
- 항상 실행
whenComplete (BiConsumer):
- 결과 + 예외 관찰
- 변환 X (그대로 전달)
// exceptionally — 예외 시 대체값
CompletableFuture.supplyAsync(() -> riskyTask())
.exceptionally(ex -> {
log.error("실패", ex);
return defaultValue(); // 대체값
});
// 정상: 원래 결과, 예외: 대체값
// handle — 결과 + 예외 (항상)
CompletableFuture.supplyAsync(() -> riskyTask())
.handle((result, ex) -> {
if (ex != null) {
log.error("실패", ex);
return defaultValue();
}
return process(result);
});
// 정상/예외 모두 처리 (변환 가능)
// whenComplete — 관찰 (변환 X)
CompletableFuture.supplyAsync(() -> task())
.whenComplete((result, ex) -> {
if (ex != null) {
log.error("실패", ex);
} else {
log.info("성공: {}", result);
}
// 결과/예외 그대로 전달 (변환 X)
});
| 메서드 | 입력 | 반환 | 용도 |
|---|---|---|---|
| exceptionally | 예외 | 대체값 | 예외만 처리 |
| handle | 결과+예외 | 새 값 | 둘 다 (변환) |
| whenComplete | 결과+예외 | 그대로 | 관찰 (로깅) |
@Service
public class ExceptionHandling {
private final ExecutorService executor = Executors.newFixedThreadPool(10);
// exceptionally — 대체값
public CompletableFuture<TrackingInfo> fetchTracking(String blNo) {
return CompletableFuture
.supplyAsync(() -> trackingApi.fetch(blNo), executor)
.exceptionally(ex -> {
log.warn("추적 실패: {}", blNo, ex);
return TrackingInfo.unknown(); // 대체값
});
}
// handle — 결과 + 예외
public CompletableFuture<ProcessResult> process(Shipment shipment) {
return CompletableFuture
.supplyAsync(() -> doProcess(shipment), executor)
.handle((result, ex) -> {
if (ex != null) {
return ProcessResult.failed(ex.getMessage());
}
return ProcessResult.success(result);
});
}
// whenComplete — 로깅 (관찰)
public CompletableFuture<BigDecimal> calculate(Shipment shipment) {
return CompletableFuture
.supplyAsync(() -> calculateFreight(shipment), executor)
.whenComplete((freight, ex) -> {
if (ex != null) log.error("계산 실패", ex);
else log.info("운임: {}", freight);
// 결과 그대로 전달
});
}
private BigDecimal doProcess(Shipment s) { return s.getWeight(); }
private BigDecimal calculateFreight(Shipment s) { return s.getWeight(); }
record TrackingInfo() { static TrackingInfo unknown() { return new TrackingInfo(); } }
record ProcessResult(String status) {
static ProcessResult success(Object r) { return new ProcessResult("OK"); }
static ProcessResult failed(String m) { return new ProcessResult("FAIL"); }
}
}
exceptionally / handle / whenComplete 차이는?
답:
1. exceptionally:
handle:
whenComplete:
선택:
// allOf — 모두 완료
CompletableFuture<Void> allOf(CompletableFuture<?>... cfs);
// anyOf — 하나 완료
CompletableFuture<Object> anyOf(CompletableFuture<?>... cfs);
// allOf — 모든 작업 완료 대기
CompletableFuture<String> cf1 = CompletableFuture.supplyAsync(() -> task1());
CompletableFuture<String> cf2 = CompletableFuture.supplyAsync(() -> task2());
CompletableFuture<String> cf3 = CompletableFuture.supplyAsync(() -> task3());
CompletableFuture<Void> all = CompletableFuture.allOf(cf1, cf2, cf3);
all.thenRun(() -> {
// 모두 완료 후
log.info("모두 완료");
});
// allOf 는 Void → 결과 수집은 join
CompletableFuture<List<String>> results =
CompletableFuture.allOf(cf1, cf2, cf3)
.thenApply(v -> Stream.of(cf1, cf2, cf3)
.map(CompletableFuture::join) // 이미 완료됨
.toList());
// anyOf — 가장 빠른 하나
CompletableFuture<Object> any = CompletableFuture.anyOf(cf1, cf2, cf3);
any.thenAccept(result -> {
// 가장 먼저 완료된 결과
log.info("첫 결과: {}", result);
});
활용:
allOf:
- 여러 작업 모두 완료
- 결과 종합
- 병렬 처리 후 집계
anyOf:
- 가장 빠른 응답
- 여러 소스 중 하나
- 타임아웃 (with 지연)
@Service
public class AllOfAnyOf {
private final ExecutorService executor = Executors.newFixedThreadPool(10);
// allOf — 모든 배송 처리
public CompletableFuture<List<Result>> processAll(List<Shipment> shipments) {
List<CompletableFuture<Result>> futures = shipments.stream()
.map(s -> CompletableFuture.supplyAsync(() -> process(s), executor))
.toList();
return CompletableFuture
.allOf(futures.toArray(new CompletableFuture[0])) // 모두 완료
.thenApply(v -> futures.stream()
.map(CompletableFuture::join)
.toList());
}
// anyOf — 여러 추적 소스 중 빠른 것
public CompletableFuture<Object> fetchFromFastest(String blNo) {
CompletableFuture<TrackingInfo> source1 = CompletableFuture
.supplyAsync(() -> api1.fetch(blNo), executor);
CompletableFuture<TrackingInfo> source2 = CompletableFuture
.supplyAsync(() -> api2.fetch(blNo), executor);
return CompletableFuture.anyOf(source1, source2); // 빠른 것
}
private Result process(Shipment s) { return new Result(); }
record Result() {}
record TrackingInfo() {}
}
allOf / anyOf의 조합은?
답:
1. allOf:
anyOf:
결과 수집:
활용:
Async 변형:
thenApply vs thenApplyAsync:
thenApply:
- 이전 작업 스레드에서 (또는 호출)
- 같은 스레드 가능
thenApplyAsync:
- 별도 스레드 풀에서
- 스레드 전환
// thenApply — 이전 스레드
CompletableFuture.supplyAsync(() -> task(), executor1)
.thenApply(r -> transform(r)); // executor1 스레드 (또는 호출)
// thenApplyAsync — 별도 스레드
CompletableFuture.supplyAsync(() -> task(), executor1)
.thenApplyAsync(r -> transform(r), executor2); // executor2
Async 사용:
thenApply:
- 가벼운 변환
- 스레드 전환 불필요
thenApplyAsync:
- 무거운 작업
- 별도 풀 필요
- 블로킹 작업
// 모든 콜백에 Async 변형
thenApply / thenApplyAsync
thenAccept / thenAcceptAsync
thenRun / thenRunAsync
thenCompose / thenComposeAsync
thenCombine / thenCombineAsync
// Async: 별도 스레드 풀
// 스레드 확인
CompletableFuture.supplyAsync(() -> {
log.info("supply: {}", Thread.currentThread().getName());
return "data";
}, executor)
.thenApply(data -> {
log.info("apply: {}", Thread.currentThread().getName()); // 같은 또는 호출
return data;
})
.thenApplyAsync(data -> {
log.info("applyAsync: {}", Thread.currentThread().getName()); // 별도
return data;
}, executor2);
@Service
public class AsyncVariants {
private final ExecutorService cpuExecutor = Executors.newFixedThreadPool(4);
private final ExecutorService ioExecutor = Executors.newFixedThreadPool(20);
public CompletableFuture<Result> process(Shipment shipment) {
return CompletableFuture
// I/O — ioExecutor
.supplyAsync(() -> fetchData(shipment), ioExecutor)
// CPU 변환 — cpuExecutor (Async 로 전환)
.thenApplyAsync(data -> heavyCompute(data), cpuExecutor)
// 가벼운 변환 — 같은 스레드 (Async X)
.thenApply(computed -> wrap(computed))
// I/O 저장 — ioExecutor
.thenAcceptAsync(result -> save(result), ioExecutor)
.thenApply(v -> new Result());
}
private Object fetchData(Shipment s) { return null; }
private Object heavyCompute(Object d) { return null; }
private Object wrap(Object c) { return null; }
private void save(Object r) { }
record Result() {}
}
thenApply vs thenApplyAsync 차이는?
답:
1. thenApply:
thenApplyAsync:
모든 then*:
선택:
// 비동기 파이프라인
public CompletableFuture<Order> processOrder(Long orderId) {
return CompletableFuture
.supplyAsync(() -> fetchOrder(orderId), executor)
.thenCompose(order -> validateAsync(order))
.thenCompose(order -> calculatePriceAsync(order))
.thenApply(order -> applyDiscount(order))
.exceptionally(ex -> {
log.error("주문 처리 실패", ex);
return Order.failed(orderId);
});
}
// 여러 외부 API 병렬 + 집계
public CompletableFuture<Dashboard> buildDashboard(Long userId) {
CompletableFuture<Profile> profileF =
CompletableFuture.supplyAsync(() -> fetchProfile(userId), executor);
CompletableFuture<List<Order>> ordersF =
CompletableFuture.supplyAsync(() -> fetchOrders(userId), executor);
CompletableFuture<Stats> statsF =
CompletableFuture.supplyAsync(() -> fetchStats(userId), executor);
return CompletableFuture.allOf(profileF, ordersF, statsF)
.thenApply(v -> new Dashboard(
profileF.join(), ordersF.join(), statsF.join()));
// 3개 병렬 → 집계
}
// orTimeout (Java 9+)
CompletableFuture.supplyAsync(() -> slowTask(), executor)
.orTimeout(5, TimeUnit.SECONDS) // 5초 타임아웃
.exceptionally(ex -> {
if (ex instanceof TimeoutException) {
return defaultValue();
}
throw new CompletionException(ex);
});
// completeOnTimeout (Java 9+)
CompletableFuture.supplyAsync(() -> slowTask(), executor)
.completeOnTimeout(defaultValue(), 5, TimeUnit.SECONDS);
// 최종 결과 회수 (필요 시)
CompletableFuture<Result> cf = processAsync();
// join() — unchecked 예외
Result result = cf.join(); // CompletionException
// get() — checked 예외
Result result2 = cf.get(); // ExecutionException, InterruptedException
CompletableFuture 주의:
- commonPool 블로킹 회피 (커스텀 Executor)
- 예외 처리 누락 (조용히 사라짐)
- join/get 블로킹 (체이닝 끝에만)
- 무거운 작업 Async + 별도 풀
@Service
public class CompletableFutureRealWorld {
private final ExecutorService executor = Executors.newFixedThreadPool(20);
// 배송 종합 정보 (병렬 + 집계 + 타임아웃)
public CompletableFuture<ShipmentDetail> getShipmentDetail(Long shipmentId) {
CompletableFuture<Shipment> shipmentF = CompletableFuture
.supplyAsync(() -> findShipment(shipmentId), executor);
CompletableFuture<TrackingInfo> trackingF = CompletableFuture
.supplyAsync(() -> fetchTracking(shipmentId), executor)
.orTimeout(5, TimeUnit.SECONDS)
.exceptionally(ex -> TrackingInfo.unknown()); // 타임아웃 시 대체
CompletableFuture<List<Document>> docsF = CompletableFuture
.supplyAsync(() -> fetchDocuments(shipmentId), executor);
return CompletableFuture.allOf(shipmentF, trackingF, docsF)
.thenApply(v -> new ShipmentDetail(
shipmentF.join(),
trackingF.join(),
docsF.join()))
.exceptionally(ex -> {
log.error("배송 상세 조회 실패: {}", shipmentId, ex);
return ShipmentDetail.empty();
});
// 3개 병렬 + 타임아웃 + 예외 처리 + 집계
}
private Shipment findShipment(Long id) { return null; }
private TrackingInfo fetchTracking(Long id) { return new TrackingInfo(); }
private List<Document> fetchDocuments(Long id) { return List.of(); }
record TrackingInfo() { static TrackingInfo unknown() { return new TrackingInfo(); } }
record Document() {}
record ShipmentDetail(Shipment s, TrackingInfo t, List<Document> d) {
static ShipmentDetail empty() { return new ShipmentDetail(null, null, null); }
}
}
CompletableFuture의 실무 활용은?
답:
1. 파이프라인:
병렬 + 집계:
타임아웃:
회수:
| Q | 핵심 답변 |
|---|---|
| CompletableFuture? | 논블로킹 콜백 (Future 극복) |
| supplyAsync vs runAsync? | 결과 O vs X |
| thenApply/Accept/Run? | 변환/소비/무관 |
| thenCompose vs Combine? | 의존 vs 독립 |
| exceptionally/handle? | 대체값 vs 결과+예외 |
| allOf/anyOf? | 모두/하나 |
| thenApply vs Async? | 같은 vs 별도 스레드 |
| commonPool 주의? | 블로킹 고갈 |
| join vs get? | unchecked vs checked |
| 타임아웃? | orTimeout |
Q1. CompletableFuture? → 논블로킹 콜백
Q2. Future 극복? → 블로킹, 조합
Q3. CompletionStage? → 비동기 단계
Q4. supplyAsync? → Supplier (결과)
Q5. runAsync? → Runnable (결과 X)
Q6. 기본 Executor? → commonPool
Q7. commonPool 주의? → 블로킹 고갈
Q8. completedFuture? → 완료된 CF
Q9. complete()? → 수동 완료
Q10. 콜백? → then*
Q11. 논블로킹? → 완료 시 자동
Q12. 체이닝? → 연결
Q13. thenApply? → 변환 (Function)
Q14. thenAccept? → 소비 (Consumer)
Q15. thenRun? → 무관 (Runnable)
Q16. thenApply 반환? → 새 결과
Q17. thenAccept 반환? → Void
Q18. thenCompose? → flatMap (의존)
Q19. thenCombine? → zip (독립)
Q20. Compose vs Apply? → 평탄 vs 중첩
Q21. Combine 두 작업? → 병렬
Q22. 변환 메서드? → thenApply
Q23. 소비 메서드? → thenAccept
Q24. 의존 조합? → thenCompose
Q25. 독립 조합? → thenCombine
Q26. exceptionally? → 예외 시 대체값
Q27. handle? → 결과 + 예외
Q28. whenComplete? → 관찰 (변환 X)
Q29. handle 변환? → 가능
Q30. whenComplete 변환? → X
Q31. allOf? → 모두 완료
Q32. anyOf? → 하나 완료
Q33. allOf 반환? → Void
Q34. allOf 결과? → join 으로
Q35. anyOf 결과? → 빠른 것
Q36. 예외 전파? → CompletionException
Q37. 예외 누락? → 조용히 사라짐
Q38. exceptionally 정상? → 원래 결과
Q39. thenApply 스레드? → 이전 또는 호출
Q40. thenApplyAsync? → 별도 풀
Q41. Async 변형? → 모든 then*
Q42. Async 사용? → 무거운 작업
Q43. join()? → unchecked (CompletionException)
Q44. get()? → checked (ExecutionException)
Q45. orTimeout? → 타임아웃 (Java 9+)
Q46. completeOnTimeout? → 타임아웃 시 기본값
Q47. 파이프라인? → supply → compose → apply
Q48. 병렬 집계? → allOf + join
Q49. 커스텀 Executor? → 권장
Q50. 블로킹 작업? → Async + 별도 풀
50 / 50 → CompletableFuture 마스터
45-49 → 거의 마스터
40-44 → 복습
< 40 → Unit 8.1 재학습
답:
답:
답:
답:
답:
1. CompletableFuture
2. 콜백과 조합
3. 예외와 스레드
이번 Unit에서 CompletableFuture 를 봤다면, 다음은 ForkJoinPool (분할 정복).
🚀 Phase 8 — 고급 비동기
✅ Unit 8.1 CompletableFuture (★ 마스터) ← 여기
⏭ Unit 8.2 ForkJoinPool
⏭ Unit 8.3 RecursiveTask — 4주차 완주
✅ Phase 1~7 (32 Unit, 1·2차 정점 완료)
🚀 Phase 8 — 고급 비동기 (1/3 진행)
총: 33/35 Unit