CompletableFuture 异步编程
掌握异步编排、thenApply、thenCombine、异常处理
CompletableFuture 异步编程
前面学了用线程池提交任务,但如果多个任务之间有依赖关系(A 完成后 B 才能开始,C 需要 A 和 B 的结果),用 Future.get() 阻塞等待会让代码又臭又长。CompletableFuture 用链式声明解决这个问题——你只管描述"先做什么、再做什么",不用手动等。
学完本章你将: 创建异步任务、链式编排、组合任务、异常处理。
为什么需要 CompletableFuture
java
// ❌ 传统 Future.get() —— 阻塞等待,链式困难
Future<User> userFuture = pool.submit(() -> fetchUser(1));
User user = userFuture.get(); // 阻塞!当前线程卡住
Future<List<Order>> orderFuture = pool.submit(() -> fetchOrders(user.getId()));
List<Order> orders = orderFuture.get(); // 又阻塞!
// ✅ CompletableFuture —— 声明式链式
CompletableFuture.supplyAsync(() -> fetchUser(1))
.thenApply(user -> fetchOrders(user.getId())) // 自动等 user 完成再执行
.thenAccept(orders -> System.out.println(orders)); // 自动等 orders 完成
创建与获取
java
// 无返回值
CompletableFuture<Void> f1 = CompletableFuture.runAsync(() -> {
System.out.println("异步执行");
});
// 有返回值 —— 用 supplyAsync
CompletableFuture<String> f2 = CompletableFuture.supplyAsync(() -> {
return "返回结果";
});
// 阻塞等待(测试时用)
String result = f2.get(); // 阻塞直到完成
String result2 = f2.join(); // 效果一样,但不抛受检异常
链式处理 —— 流水线
每个 then* 方法都返回新的 CompletableFuture,让你像搭积木一样串联操作:
java
CompletableFuture.supplyAsync(() -> fetchUser(1)) // 异步获取用户
.thenApply(user -> user.getName()) // 拿到用户 → 取名字
.thenApply(String::toUpperCase) // 转大写
.thenAccept(name -> System.out.println("结果:" + name)) // 最终消费
.thenRun(() -> System.out.println("流程结束")); // 最后执行
| 方法 | 做什么 | 有无入参 |
|---|---|---|
| `thenApply` | 转换结果 | ✅ 拿上一步结果 |
| `thenAccept` | 消费结果 | ✅ |
| `thenRun` | 最后收尾 | ❌ |
组合多个任务 —— 等这个也等那个
java
CompletableFuture<String> hello = supplyAsync(() -> "Hello");
CompletableFuture<String> world = supplyAsync(() -> "World");
// thenCombine:两个都完成,合并结果
hello.thenCombine(world, (h, w) -> h + " " + w)
.thenAccept(System.out::println); // "Hello World"
// allOf:等所有完成(不合并结果)
CompletableFuture.allOf(hello, world).join();
// anyOf:任意一个完成就往下走
CompletableFuture.anyOf(hello, world).thenAccept(System.out::println);
异常处理
java
CompletableFuture.supplyAsync(() -> {
if (Math.random() > 0.5) throw new RuntimeException("炸了");
return "成功";
})
.exceptionally(ex -> "默认值") // 异常时兜底
.thenAccept(System.out::println); // 总能拿到值