02.02.02 CompletableFuture 组合与异常处理:构建复杂异步流
导读
在实际项目中,我们经常需要并行执行多个异步任务,然后合并它们的结果;或者需要处理复杂的异常场景,实现优雅降级。这正是 CompletableFuture 组合 API 大显身手的地方。
本文将深入探讨 CompletableFuture 的组合模式和异常处理机制,帮助你构建健壮的异步流程。
适用人群:已掌握 CompletableFuture 基础 API 的开发者
学习目标:
- 掌握
thenCombine、allOf、anyOf等组合方法 - 理解异常传播机制和处理策略
- 能够设计容错的异步流程
一、任务组合模式
1.1 thenCombine:合并两个独立任务
当两个异步任务相互独立,需要等待两者都完成后合并结果时,使用 thenCombine:
java
// 场景:并行获取用户信息和订单信息,然后合并
CompletableFuture<User> userFuture = CompletableFuture
.supplyAsync(() -> userService.getUser(userId), ioExecutor);
CompletableFuture<List<Order>> ordersFuture = CompletableFuture
.supplyAsync(() -> orderService.getOrders(userId), ioExecutor);
// 两个任务并行执行,完成后合并
CompletableFuture<UserProfile> profileFuture = userFuture.thenCombine(
ordersFuture,
(user, orders) -> new UserProfile(user, orders)
);
// 性能对比
// 串行:time = time(user) + time(orders) = 2s
// 并行:time = max(time(user), time(orders)) = 1s
三种变体:
java
// thenCombine:合并两个结果 (T, U) -> V
CompletableFuture<V> combined = cf1.thenCombine(cf2, (t, u) -> combine(t, u));
// thenAcceptBoth:消费两个结果 (T, U) -> void
CompletableFuture<Void> consumed = cf1.thenAcceptBoth(cf2, (t, u) -> process(t, u));
// runAfterBoth:两个都完成后执行 () -> void
CompletableFuture<Void> after = cf1.runAfterBoth(cf2, () -> cleanup());
1.2 allOf:等待所有任务完成
当需要等待多个任务全部完成时,使用 allOf:
java
// 场景:并行调用多个服务,等待全部完成
CompletableFuture<String> task1 = fetchFromService1();
CompletableFuture<String> task2 = fetchFromService2();
CompletableFuture<String> task3 = fetchFromService3();
// allOf 返回 CompletableFuture<Void>,只关心完成状态
CompletableFuture<Void> allDone = CompletableFuture.allOf(task1, task2, task3);
// 收集结果(需要手动处理)
CompletableFuture<List<String>> results = allDone.thenApply(v -> {
List<String> list = new ArrayList<>();
list.add(task1.join()); // 已完成,join() 不会阻塞
list.add(task2.join());
list.add(task3.join());
return list;
});
通用工具方法:
java
// 封装:将多个 CompletableFuture 的结果收集为 List
public static <T> CompletableFuture<List<T>> allOfList(
List<CompletableFuture<T>> futures) {
CompletableFuture<Void> allDone = CompletableFuture.allOf(
futures.toArray(new CompletableFuture[0])
);
return allDone.thenApply(v ->
futures.stream()
.map(CompletableFuture::join)
.collect(Collectors.toList())
);
}
// 使用
List<CompletableFuture<User>> userFutures = userIds.stream()
.map(id -> getUserAsync(id))
.collect(Collectors.toList());
CompletableFuture<List<User>> allUsers = allOfList(userFutures);
带异常处理的收集:
java
// 即使部分任务失败,也收集成功的结果
public static <T> CompletableFuture<List<T>> allOfWithFallback(
List<CompletableFuture<T>> futures, T fallback) {
// 为每个任务添加异常处理
List<CompletableFuture<T>> safeFutures = futures.stream()
.map(f -> f.exceptionally(ex -> {
log.warn("任务失败,使用默认值", ex);
return fallback;
}))
.collect(Collectors.toList());
return allOfList(safeFutures);
}
1.3 anyOf:任一任务完成
当只需要最快完成的结果时,使用 anyOf:
java
// 场景:从多个数据源获取,使用最快的结果
CompletableFuture<String> source1 = fetchFromCache(); // 快
CompletableFuture<String> source2 = fetchFromDB(); // 中
CompletableFuture<String> source3 = fetchFromRemote(); // 慢
// anyOf 返回 Object,需要类型转换
CompletableFuture<Object> fastest = CompletableFuture.anyOf(
source1, source2, source3
);
String result = (String) fastest.join();
// 取消其他任务(节省资源)
source1.cancel(true);
source2.cancel(true);
source3.cancel(true);
超时控制模式:
java
// 使用 anyOf 实现超时
CompletableFuture<String> task = fetchData();
CompletableFuture<String> timeout = CompletableFuture
.supplyAsync(() -> {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
throw new TimeoutException("超时");
});
CompletableFuture<Object> result = CompletableFuture.anyOf(task, timeout);
1.4 Either 模式:任一完成即处理
java
// applyToEither:任一完成后转换
CompletableFuture<String> result = source1.applyToEither(
source2,
data -> process(data) // 处理先完成的结果
);
// acceptEither:任一完成后消费
CompletableFuture<Void> consumed = source1.acceptEither(
source2,
data -> System.out.println("收到: " + data)
);
// runAfterEither:任一完成后执行
CompletableFuture<Void> after = source1.runAfterEither(
source2,
() -> log.info("有一个任务完成了")
);
二、异常处理机制
2.1 异常传播规则
CompletableFuture 中的异常会沿着链向下传播,直到被处理:
java
CompletableFuture<String> future = CompletableFuture
.supplyAsync(() -> {
throw new RuntimeException("任务失败");
})
.thenApply(s -> s.toUpperCase()) // ❌ 不会执行
.thenApply(s -> s + " processed"); // ❌ 不会执行
// 最终调用 get()/join() 时抛出 CompletionException
try {
future.join();
} catch (CompletionException e) {
System.out.println("捕获异常: " + e.getCause().getMessage());
}
异常包装规则:
java
// CompletableFuture 内部异常会被包装
// 原始异常 -> CompletionException(原始异常)
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
throw new IllegalArgumentException("参数错误");
});
try {
future.join();
} catch (CompletionException e) {
Throwable cause = e.getCause(); // IllegalArgumentException
System.out.println(cause.getClass().getName()); // IllegalArgumentException
}
2.2 exceptionally:捕获并恢复
java
// 捕获异常,返回默认值
CompletableFuture<String> future = CompletableFuture
.supplyAsync(() -> {
if (Math.random() > 0.5) {
throw new RuntimeException("失败");
}
return "成功";
})
.exceptionally(ex -> {
log.error("任务失败", ex);
return "默认值"; // 恢复为正常值
});
// 链式异常处理
CompletableFuture<String> result = fetchData()
.exceptionally(ex -> {
if (ex instanceof NetworkException) {
return tryFromCache(); // 网络异常,尝试缓存
}
throw new RuntimeException(ex); // 其他异常继续传播
})
.exceptionally(ex -> {
return "最终默认值"; // 最后的兜底
});
2.3 handle:统一处理成功与失败
java
// handle 同时处理成功和失败,可以改变结果
CompletableFuture<String> future = CompletableFuture
.supplyAsync(() -> riskyOperation())
.handle((result, ex) -> {
if (ex != null) {
// 处理异常
log.error("操作失败", ex);
return "错误: " + ex.getMessage();
}
// 处理成功
return "成功: " + result;
});
// 对比 whenComplete 和 handle
// whenComplete:执行副作用,不改变结果,异常继续传播
// handle:可以改变结果,可以恢复异常
2.4 异常分类处理
java
// 根据异常类型采取不同策略
CompletableFuture<String> result = fetchData()
.exceptionally(ex -> {
Throwable cause = ex.getCause() != null ? ex.getCause() : ex;
if (cause instanceof TimeoutException) {
log.warn("请求超时,使用缓存");
return cache.get(key);
}
if (cause instanceof ValidationException) {
log.error("参数校验失败");
throw new BusinessException("参数错误", cause);
}
if (cause instanceof NetworkException) {
log.warn("网络异常,重试");
return retryFetch();
}
// 未知异常
log.error("未知错误", cause);
throw new SystemException("系统错误", cause);
});
2.5 多层异常处理
java
// 设计多层异常处理策略
CompletableFuture<OrderResult> result = CompletableFuture
.supplyAsync(() -> orderService.createOrder(request))
// 第一层:业务异常处理
.exceptionally(ex -> {
if (ex.getCause() instanceof InsufficientStockException) {
return OrderResult.outOfStock();
}
if (ex.getCause() instanceof PaymentFailedException) {
return OrderResult.paymentFailed();
}
throw new RuntimeException(ex); // 其他异常继续传播
})
// 第二层:日志记录
.whenComplete((r, ex) -> {
if (ex != null) {
log.error("订单创建失败", ex);
metrics.increment("order.failure");
} else {
log.info("订单创建成功: {}", r.getOrderId());
metrics.increment("order.success");
}
})
// 第三层:最终兜底
.handle((r, ex) -> {
if (ex != null) {
return OrderResult.systemError(ex.getMessage());
}
return r;
});
三、超时控制(JDK 9+)
3.1 orTimeout:超时抛异常
java
// 超时后抛出 TimeoutException
CompletableFuture<String> future = CompletableFuture
.supplyAsync(() -> slowOperation())
.orTimeout(2, TimeUnit.SECONDS) // 2 秒超时
.exceptionally(ex -> {
if (ex instanceof TimeoutException) {
return "超时,使用默认值";
}
throw new RuntimeException(ex);
});
3.2 completeOnTimeout:超时使用默认值
java
// 超时后使用默认值,不抛异常
CompletableFuture<String> future = CompletableFuture
.supplyAsync(() -> slowOperation())
.completeOnTimeout("默认值", 2, TimeUnit.SECONDS);
3.3 JDK 8 兼容方案
java
// JDK 8 实现超时控制
public <T> CompletableFuture<T> withTimeout(
CompletableFuture<T> future, long timeout, TimeUnit unit) {
ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
CompletableFuture<T> result = new CompletableFuture<>();
// 设置超时任务
ScheduledFuture<?> timeoutTask = scheduler.schedule(() -> {
if (!result.isDone()) {
result.completeExceptionally(
new TimeoutException("操作超时: " + timeout + " " + unit)
);
}
}, timeout, unit);
// 原任务完成时,取消超时任务
future.whenComplete((value, ex) -> {
timeoutTask.cancel(false);
if (ex != null) {
result.completeExceptionally(ex);
} else {
result.complete(value);
}
});
return result;
}
// 使用
CompletableFuture<String> future = withTimeout(
fetchData(), 2, TimeUnit.SECONDS
);
四、组合模式实战
4.1 API 聚合网关
java
public CompletableFuture<Dashboard> getDashboard(String userId) {
// 并行获取三个数据源
CompletableFuture<Profile> profileFuture = CompletableFuture
.supplyAsync(() -> userApi.getProfile(userId), ioExecutor);
CompletableFuture<List<Order>> ordersFuture = CompletableFuture
.supplyAsync(() -> orderApi.getRecentOrders(userId), ioExecutor);
CompletableFuture<List<Coupon>> couponsFuture = CompletableFuture
.supplyAsync(() -> couponApi.getAvailableCoupons(userId), ioExecutor);
// 等待所有完成,设置超时
return CompletableFuture.allOf(profileFuture, ordersFuture, couponsFuture)
.orTimeout(2, TimeUnit.SECONDS)
.thenApply(v -> new Dashboard(
profileFuture.join(),
ordersFuture.join(),
couponsFuture.join()
))
.exceptionally(ex -> {
log.error("聚合失败", ex);
return Dashboard.fallback(userId);
});
}
4.2 带降级的聚合
java
public CompletableFuture<Dashboard> getDashboardWithFallback(String userId) {
// 每个任务独立处理异常,不影响其他任务
CompletableFuture<Profile> profileFuture = CompletableFuture
.supplyAsync(() -> userApi.getProfile(userId), ioExecutor)
.orTimeout(1, TimeUnit.SECONDS)
.exceptionally(ex -> {
log.warn("获取用户信息失败", ex);
return Profile.anonymous();
});
CompletableFuture<List<Order>> ordersFuture = CompletableFuture
.supplyAsync(() -> orderApi.getRecentOrders(userId), ioExecutor)
.orTimeout(1, TimeUnit.SECONDS)
.exceptionally(ex -> {
log.warn("获取订单失败", ex);
return Collections.emptyList();
});
CompletableFuture<List<Coupon>> couponsFuture = CompletableFuture
.supplyAsync(() -> couponApi.getAvailableCoupons(userId), ioExecutor)
.orTimeout(1, TimeUnit.SECONDS)
.exceptionally(ex -> {
log.warn("获取优惠券失败", ex);
return Collections.emptyList();
});
// 即使部分失败,也能返回有效结果
return CompletableFuture.allOf(profileFuture, ordersFuture, couponsFuture)
.thenApply(v -> new Dashboard(
profileFuture.join(),
ordersFuture.join(),
couponsFuture.join()
));
}
4.3 级联异步调用
java
public CompletableFuture<String> cascadingCall(String userId) {
// 第一级:获取用户(超时 1 秒)
CompletableFuture<User> userFuture = CompletableFuture
.supplyAsync(() -> userService.getUser(userId), ioExecutor)
.orTimeout(1, TimeUnit.SECONDS)
.exceptionally(ex -> User.defaultUser());
// 第二级:获取订单(依赖用户,超时 2 秒)
CompletableFuture<List<Order>> ordersFuture = userFuture
.thenCompose(user -> CompletableFuture
.supplyAsync(() -> orderService.getOrders(user.getId()), ioExecutor)
.orTimeout(2, TimeUnit.SECONDS)
.exceptionally(ex -> Collections.emptyList())
);
// 第三级:获取优惠券(与订单并行,超时 1 秒)
CompletableFuture<List<Coupon>> couponsFuture = userFuture
.thenCompose(user -> CompletableFuture
.supplyAsync(() -> couponService.getCoupons(user.getId()), ioExecutor)
.orTimeout(1, TimeUnit.SECONDS)
.exceptionally(ex -> Collections.emptyList())
);
// 合并结果
return ordersFuture.thenCombine(couponsFuture, (orders, coupons) -> {
return buildResponse(orders, coupons);
});
}
五、组合策略对比
| 方法 | 输入 | 返回 | 用途 |
|---|---|---|---|
thenCombine |
2 个 CF | CF<V> |
合并两个结果 |
thenAcceptBoth |
2 个 CF | CF<Void> |
消费两个结果 |
allOf |
N 个 CF | CF<Void> |
等待所有完成 |
anyOf |
N 个 CF | CF<Object> |
任一完成 |
applyToEither |
2 个 CF | CF<U> |
任一完成后转换 |
acceptEither |
2 个 CF | CF<Void> |
任一完成后消费 |
六、异常处理对比
| 方法 | 输入 | 改变结果 | 异常传播 |
|---|---|---|---|
exceptionally |
Throwable -> T |
✅ 可以恢复 | ❌ 阻断 |
whenComplete |
(T, Throwable) -> void |
❌ 不改变 | ✅ 继续 |
handle |
(T, Throwable) -> U |
✅ 可以转换 | ❌ 阻断 |
七、最佳实践
7.1 设计原则
- 快速失败 vs 优雅降级:根据业务需求选择
- 独立异常处理:每个任务独立处理异常,避免连锁失败
- 超时控制:所有远程调用必须设置超时
- 日志追踪 :使用
whenComplete记录日志,不影响结果
7.2 常见陷阱
java
// ❌ 陷阱1:忘记处理异常
CompletableFuture.supplyAsync(() -> riskyOperation())
.thenApply(result -> process(result));
// 异常会被吞掉!
// ✅ 正确:添加异常处理
CompletableFuture.supplyAsync(() -> riskyOperation())
.thenApply(result -> process(result))
.exceptionally(ex -> {
log.error("处理失败", ex);
return defaultValue;
});
// ❌ 陷阱2:exceptionally 中再次抛异常
.exceptionally(ex -> {
throw new RuntimeException(ex); // 新异常继续传播
});
// ❌ 陷阱3:allOf 不返回结果
CompletableFuture<Void> all = CompletableFuture.allOf(f1, f2);
// all.join() 只是 void,需要手动从 f1, f2 获取结果
八、总结
CompletableFuture 的组合 API 让我们能够构建复杂的异步流程:
- 组合模式 :
thenCombine合并、allOf等待全部、anyOf取最快 - 异常处理 :
exceptionally恢复、handle统一处理、whenComplete副作用 - 超时控制 :
orTimeout(JDK 9+)或自定义实现
核心原则:每个异步任务独立处理异常,设置合理超时,提供降级策略。
上一篇回顾 : 《CompletableFuture API 入门:告别阻塞式 Future》
我们从零开始掌握 CompletableFuture 的核心 APICompletableFuture。
下一篇预告 : 《CompletableFuture 实战案例:电商订单与 API 聚合》我们将通过完整的业务案例,展示如何在真实项目中应用 CompletableFuture。