CompletableFuture & Async Composition
Học cách compose computation bất đồng bộ mà không biến pipeline thành chuỗi get()/join() blocking.
1. Mục tiêu
- Hiểu Future là “handle cho kết quả chưa sẵn sàng”.
- Thấy vì sao
Future.get()dễ kéo code trở lại blocking. - Dùng CompletionStage để compose thay vì chờ.
- Nắm chắc
thenApplyvsthenCompose. - Hiểu thread/executor semantics của continuation.
2. Future: có async nhưng chưa compose tốt
ExecutorService pool = Executors.newFixedThreadPool(4);
Future<User> future = pool.submit(() -> repository.loadUser(42));
User user = future.get(); // block nếu chưa hoàn thành
Future cho biết computation có thể chưa xong, hỗ trợ cancellation và lấy kết quả; nhưng API cổ điển khiến ta thường phải gọi get(), tức chờ.
3. CompletableFuture
CompletableFuture<User> userFuture =
CompletableFuture.supplyAsync(() -> repository.loadUser(42));
userFuture
.thenApply(User::name)
.thenAccept(System.out::println);
Thay vì “đợi rồi làm tiếp”, ta mô tả stage tiếp theo sẽ chạy khi stage trước hoàn thành.
4. thenApply vs thenCompose
thenApply — synchronous transform của value
CompletableFuture<User> user = findUserAsync(42);
CompletableFuture<String> name =
user.thenApply(User::name);
thenCompose — transform trả về một async stage khác
CompletableFuture<Order> order =
findUserAsync(42)
.thenCompose(user -> findLatestOrderAsync(user.id()));
Sai mental model
CompletableFuture<CompletableFuture<Order>> nested =
findUserAsync(42)
.thenApply(user -> findLatestOrderAsync(user.id()));
thenCompose ↔ Reactor flatMap ↔ Vert.x compose.5. Chạy các async task độc lập
CompletableFuture<User> user = findUserAsync(id);
CompletableFuture<List<Order>> orders = findOrdersAsync(id);
CompletableFuture<UserProfile> profile =
user.thenCombine(orders, UserProfile::new);
thenCombine phù hợp khi hai stage độc lập và ta cần cả hai kết quả.
allOf
CompletableFuture<Void> all =
CompletableFuture.allOf(a, b, c);
allOf hoàn tất khi tất cả futures hoàn tất nhưng trả Void; cần lấy result từ từng future sau khi aggregate hoàn thành.
6. Error handling
findUserAsync(id)
.thenCompose(this::loadOrdersAsync)
.exceptionally(ex -> {
log.error("flow failed", ex);
return List.of();
});
| API | Dùng khi |
|---|---|
exceptionally | Recover từ exception thành value. |
handle | Muốn xử lý cả success và failure và transform kết quả. |
whenComplete | Side effect quan sát success/failure, thường không thay đổi result. |
7. Continuation chạy thread nào?
future.thenApply(v -> transform(v));
Non-async dependent action có thể chạy trên thread hoàn tất current future hoặc thread khác đang gọi completion method.
future.thenApplyAsync(v -> transform(v));
Async variant dùng executor mặc định của CompletableFuture nếu không truyền executor riêng; với các static async factory thông thường đây thường là ForkJoinPool.commonPool() khi environment cho phép.
future.thenApplyAsync(this::transform, myExecutor);
8. Timeout và cancellation
CompletableFuture<User> result =
findUserAsync(id)
.orTimeout(2, TimeUnit.SECONDS);
CompletableFuture<User> fallback =
findUserAsync(id)
.completeOnTimeout(User.unknown(), 2, TimeUnit.SECONDS);
Timeout của wrapper không đồng nghĩa downstream I/O chắc chắn đã bị hủy. Cancellation cần được propagate tới resource/API cụ thể nếu muốn tiết kiệm công việc thật sự.
9. End-to-end async flow
CompletableFuture<OrderResponse> createOrder(CreateOrder cmd) {
return findUserAsync(cmd.userId())
.thenCompose(user -> checkInventoryAsync(cmd)
.thenCombine(
loadPricingAsync(cmd),
(inventory, pricing) ->
new ValidatedOrder(user, inventory, pricing)
))
.thenCompose(this::chargePaymentAsync)
.thenCompose(this::saveOrderAsync)
.thenApply(OrderResponse::from)
.orTimeout(3, TimeUnit.SECONDS);
}
Điểm cần quan sát: không có get()/join() trong flow.
10. Lab
Xây aggregator gọi song song User Service và Order Service giả lập bằng ScheduledExecutorService. Yêu cầu:
- Không dùng
get()giữa pipeline. - Dùng
thenCombinecho hai call độc lập. - Timeout toàn flow ở 1500 ms.
- Nếu orders fail thì trả danh sách rỗng, nhưng nếu user fail thì fail cả request.
- In thread name ở mỗi stage để quan sát execution.
11. Bài tập
CompletableFuture<CompletableFuture<T>> thành flat flow.thenCompose ở stage trả Future.thenApply có thể chạy trên thread hoàn tất future trước.join() ngay sau supplyAsync() có thể làm mất lợi ích composition.12. Interview checkpoint
- Future và CompletableFuture khác nhau thế nào?
- thenApply vs thenCompose?
- thenCombine khác allOf?
- exceptionally vs handle vs whenComplete?
- Async continuation chạy executor nào?
- Timeout có tự hủy network call không?
- Tại sao CompletableFuture là bước đệm tốt trước Reactor?
Tài liệu và code thực hành
Đọc chapter chi tiết [legacy source: modules/04-completable-future.md] · Mở foundation labs [legacy source: labs/foundation/README.md] · Xem Java source [legacy source: examples/README.md]