前言
高并发、高吞吐已经成为现代后端系统的刚需。传统的 Future 虽然能实现异步,但多个异步任务需要编排时,代码会陷入 get() 阻塞和回调地狱。CompletableFuture 是 Java 8 引入的利器,它将函数式编程思想融入异步世界,让我们能以声明式方式组合、编排异步任务。
然而,很多同学对它还停留在 supplyAsync + thenApply 的层面,对异常处理、线程池选择、组合方式等细节不够了解。本文将结合大量示例,带你真正掌握 CompletableFuture 的正确打开方式。
一、从 Future 说起:痛点在哪里
传统 Future 只能通过 get() 阻塞获取结果,无法手动完成,多个任务之间也难以串联:
java
ExecutorService executor = Executors.newSingleThreadExecutor();
Future<String> future = executor.submit(() -> {
Thread.sleep(2000);
return "Hello";
});
String result = future.get(); // 阻塞 2 秒
System.out.println(result + " World");
问题:
-
无法注册回调,必须阻塞
-
不能将多个异步任务流水线执行
-
没有异常处理的优雅方式
CompletableFuture 实现了 Future 和 CompletionStage 接口,完美解决了上述痛点。
二、创建 CompletableFuture 的四种方式
| 方法 | 说明 |
|---|---|
CompletableFuture.supplyAsync() |
异步执行有返回值的任务 |
CompletableFuture.runAsync() |
异步执行无返回值的任务 |
CompletableFuture.completedFuture() |
直接创建一个已完成状态的 future |
new CompletableFuture<>() |
手动创建,由你控制 complete() 时机 |
java
// 1. supplyAsync
CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> "Hello");
// 2. runAsync
CompletableFuture<Void> future2 = CompletableFuture.runAsync(() -> System.out.println("run"));
// 3. 已完成 future
CompletableFuture<String> future3 = CompletableFuture.completedFuture("already done");
// 4. 手动控制
CompletableFuture<String> manual = new CompletableFuture<>();
manual.complete("手动完成");
⚠️ 默认使用
ForkJoinPool.commonPool(),建议生产环境指定自定义线程池,避免所有异步任务共享同一个公共池。
三、核心回调方法详解
3.1 thenApply / thenApplyAsync ------ 转换结果
java
CompletableFuture.supplyAsync(() -> "hello")
.thenApply(String::toUpperCase)
.thenApply(s -> s + " world")
.thenAccept(System.out::println); // HELLO WORLD
-
thenApply同步执行(沿用上一个任务的线程) -
thenApplyAsync可能另起线程(默认使用公共池)
3.2 thenAccept / thenRun ------ 消费结果/不关心结果
java
// 消费结果,无返回值
future.thenAccept(System.out::println);
// 不关心结果,仅触发动作
future.thenRun(() -> System.out.println("任务完成"));
3.3 exceptionally ------ 异常恢复
java
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
if (true) throw new RuntimeException("出错了");
return "success";
}).exceptionally(ex -> {
System.out.println("异常: " + ex.getMessage());
return "fallback value";
});
System.out.println(future.join()); // fallback value
3.4 handle ------ 同时处理正常结果和异常
java
future.handle((result, ex) -> {
if (ex != null) {
return "error";
}
return result;
});
handle与exceptionally的区别:handle 也能处理正常值,exceptionally 只处理异常。
四、多个任务的编排组合
4.1 thenCompose ------ 任务的链式依赖
类似 flatMap,避免 CompletableFuture<CompletableFuture<String>> 嵌套:
java
CompletableFuture<String> task1 = CompletableFuture.supplyAsync(() -> "id:123");
CompletableFuture<String> result = task1.thenCompose(id ->
CompletableFuture.supplyAsync(() -> queryUserById(id))
);
4.2 thenCombine ------ 两个独立任务,合并结果
java
CompletableFuture<Integer> price = CompletableFuture.supplyAsync(() -> 100);
CompletableFuture<Integer> discount = CompletableFuture.supplyAsync(() -> 20);
CompletableFuture<Integer> finalPrice = price.thenCombine(discount, (p, d) -> p - d);
System.out.println(finalPrice.join()); // 80
4.3 allOf / anyOf ------ 等待多个任务
java
List<CompletableFuture<String>> futures = Arrays.asList(
CompletableFuture.supplyAsync(() -> "A"),
CompletableFuture.supplyAsync(() -> "B")
);
// 全部完成
CompletableFuture<Void> all = CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]));
all.join();
// 任一最快完成
CompletableFuture<Object> any = CompletableFuture.anyOf(futures.toArray(new CompletableFuture[0]));
System.out.println(any.join());
注意:
allOf本身无返回值,需要结合thenApply手动收集结果。
五、最佳实践与性能陷阱
5.1 务必使用自定义线程池
java
ExecutorService bizPool = Executors.newFixedThreadPool(10,
new ThreadFactoryBuilder().setNameFormat("biz-pool-%d").build()
);
CompletableFuture.supplyAsync(() -> doHeavyTask(), bizPool)
.thenApplyAsync(this::process, bizPool) // 也指定同一池子
.exceptionally(ex -> "error");
5.2 禁止使用 join() / get() 阻塞主线程
如果不得不在非异步方法中等待,请设置超时:
java
future.get(3, TimeUnit.SECONDS); // 避免无限阻塞
5.3 正确处理异常,避免静默失败
java
// ❌ 错误:异常会被吞掉
CompletableFuture.supplyAsync(() -> {
throw new RuntimeException();
}).thenAccept(System.out::println);
// ✅ 正确:添加异常处理链
CompletableFuture.supplyAsync(() -> {
throw new RuntimeException();
}).exceptionally(ex -> {
log.error("任务失败", ex);
return null;
});
5.4 使用 thenApply 还是 thenApplyAsync?
-
如果后续处理非常轻量(如简单转换),用
thenApply可避免线程切换开销 -
如果后续处理包含 IO 或耗时计算,用
thenApplyAsync避免阻塞原线程
六、完整实战:批量查询 + 超时控制
java
public List<UserInfo> batchQueryUsers(List<String> userIds) {
ExecutorService executor = Executors.newFixedThreadPool(20);
List<CompletableFuture<UserInfo>> futures = userIds.stream()
.map(id -> CompletableFuture.supplyAsync(() -> queryUser(id), executor)
.completeOnTimeout(UserInfo.empty(), 1, TimeUnit.SECONDS) // 超时兜底
.exceptionally(ex -> UserInfo.errorUser(id)))
.collect(Collectors.toList());
return futures.stream()
.map(CompletableFuture::join) // 等待所有完成
.collect(Collectors.toList());
}
completeOnTimeout 是 Java 9+ 的方法,优雅地解决了超时问题。
总结
| 特性 | 说明 |
|---|---|
| 创建方式 | supplyAsync / runAsync / completedFuture / 手动 |
| 回调类型 | thenApply(转换)、thenAccept(消费)、thenRun(无参) |
| 异常处理 | exceptionally、handle、whenComplete |
| 任务编排 | thenCompose(链式)、thenCombine(合并)、allOf/anyOf(聚合) |
| 最佳实践 | 自定义线程池、避免阻塞、显式超时、异常兜底 |
CompletableFuture 是迈向响应式编程的第一步。掌握它,你的异步代码将变得更加清晰、健壮、可维护。如果你正在从 Future 迁移,或者项目中遇到复杂的异步依赖关系,不妨试试这套组合拳。