🌟 一、什么是 CompletableFuture?
✅ 基本定义
CompletableFuture<T> 是对 Future<T> 的增强版,实现了 异步编程 和 函数式编程风格的任务编排。
- 它表示一个可能还没有完成的计算结果。
- 支持:
- 异步执行任务
- 链式调用(函数式风格)
- 组合多个异步任务(
thenCombine,allOf,anyOf) - 异常处理
- 回调通知(
thenApply,thenAccept,thenRun等)
💡 简单说:它是“未来某个时刻会完成的任务”,你可以注册回调来处理结果或异常。
🛠️ 二、核心用法与 API 分类
一、创建异步任务
| 方法 | 说明 | 是否有返回值 | 默认线程池 |
|---|---|---|---|
runAsync(Runnable) | 异步执行一个无返回值任务 | ❌ 无 | ForkJoinPool.commonPool() |
runAsync(Runnable, Executor) | 同上,但指定线程池 | ❌ 无 | ✅ 自定义 |
supplyAsync(Supplier<T>) | 异步执行并返回结果 | ✅ 有 | ForkJoinPool.commonPool() |
supplyAsync(Supplier<T>, Executor) | 同上,但指定线程池 | ✅ 有 | ✅ 自定义 |
🔥 建议:始终使用带
Executor参数的版本,避免阻塞公共线程池。
二、链式处理结果(最常用)
这些方法都用于“前一个任务完成后,执行下一步”。区别在于是否返回值、是否异步执行。
| 方法 | 返回类型 | 是否可转换结果 | 是否异步执行 | 执行线程来源 |
|---|---|---|---|---|
thenApply(Function<T,R>) | CompletableFuture<R> | ✅ 转换结果 | ❌ 同步(在前任务线程执行) | 前一个任务的线程 |
thenApplyAsync(Function<T,R>) | CompletableFuture<R> | ✅ 转换结果 | ✅ 异步 | commonPool |
thenApplyAsync(Function<T,R>, Executor) | CompletableFuture<R> | ✅ 转换结果 | ✅ 异步 | ✅ 指定线程池 |
✅ 关键区别:
thenApply:不新开线程,在前一个任务完成的线程中执行 → 可能阻塞流水线thenApplyAsync:强制异步,提交到线程池 → 更安全,但有调度开销
📌 何时用哪个?
- 转换逻辑简单(如字符串处理)→ 用
thenApply - 转换耗时或涉及 IO → 用
thenApplyAsync(..., myExecutor)
类似方法对比:thenAccept vs thenRun
| 方法 | 参数 | 是否使用上一步结果 | 是否返回值 |
|---|---|---|---|
thenAccept(Consumer<T>) | 使用上一步结果 | ✅ 是 | ❌ 无(返回 CompletableFuture<Void>) |
thenRun(Runnable) | 不使用上一步结果 | ❌ 否 | ❌ 无 |
CompletableFuture<String> result = CompletableFuture
.supplyAsync(() -> "hello") // 第一步:返回字符串
.thenApply(s -> s + " world") // 第二步:加工
.thenApply(String::toUpperCase) // 第三步:转大写
.thenApply(s -> s + "!");
result.thenAccept(System.out::println).join();
// 输出:HELLO WORLD!
future.thenAccept(res -> System.out.println("结果是:" + res)); // 可以用 res
future.thenRun(() -> System.out.println("任务完成了")); // 拿不到 res
💡 类比:
thenAccept→ “拿到结果后做点事”thenRun→ “不管结果,只要完成了就做点事”
三、组合多个任务
1. thenCombine vs thenCompose vs thenAcceptBoth
| 方法 | 用途 | 是否组合结果 | 典型场景 |
|---|---|---|---|
thenCombine(CompletableFuture<U>, BiFunction<T,U,R>) | 两个任务都完成后,合并结果并返回新值 | ✅ 是 | 获取价格 + 税率 → 计算总价 |
thenCompose(Function<T, CompletableFuture<U>>) | 当前任务结果用于启动下一个异步任务(扁平化嵌套) | ✅ 是(链式异步) | 根据用户ID查用户 → 再查订单(异步查) |
thenAcceptBoth(CompletableFuture<U>, BiConsumer<T,U>) | 两个任务都完成后,消费两个结果,无返回值 | ❌ 否 | 打印日志、发通知 |
🔥 重点区别:
thenCombine:两个独立异步任务 → 合并结果thenCompose:异步中的异步,类似flatMap,避免CompletableFuture<CompletableFuture<T>>
CompletableFuture<Integer> priceFuture = CompletableFuture.supplyAsync(() -> {
sleep(1000);
return 100;
});
CompletableFuture<String> taxFuture = CompletableFuture.supplyAsync(() -> {
sleep(800);
return "13%";
});
CompletableFuture<String> combined = priceFuture.thenCombine(taxFuture, (price, tax) ->
"价格:" + price + ", 税率:" + tax
);
combined.thenAccept(System.out::println).join();
// 输出:价格:100, 税率:13%
// thenCompose 示例:避免嵌套
userService.findById(id)
.thenCompose(user -> orderService.findByUser(user)) // 返回 CompletableFuture<Order>
.thenAccept(order -> ...);
2. allOf vs anyOf
| 方法 | 行为 | 返回类型 | 注意事项 |
|---|---|---|---|
CompletableFuture.allOf(CompletableFuture<?>... futures) | 所有任务都完成才算完成 | CompletableFuture<Void> | ❗ 拿不到结果,需手动 .join() 每个 future |
CompletableFuture.anyOf(CompletableFuture<?>... futures) | 任一任务完成即完成 | CompletableFuture<Object> | ❗ 返回 Object,需转型;其他任务仍在运行 |
// allOf 后获取所有结果
CompletableFuture<Void> f1 = CompletableFuture.runAsync(() -> System.out.println("任务1完成"));
CompletableFuture<Void> f2 = CompletableFuture.runAsync(() -> System.out.println("任务2完成"));
CompletableFuture<Void> f3 = CompletableFuture.runAsync(() -> System.out.println("任务3完成"));
CompletableFuture<Void> allDone = CompletableFuture.allOf(f1, f2, f3);
allDone.thenRun(() -> System.out.println("全部完成!")).join();
//或者
List<String> results = CompletableFuture.allOf(f1, f2, f3)
.thenApply(v -> Stream.of(f1, f2, f3).map(CompletableFuture::join).toList())
.join();
//AnyOf
CompletableFuture<String> fastest = CompletableFuture.anyOf(
CompletableFuture.supplyAsync(() -> { sleep(2000); return "慢的"; }),
CompletableFuture.supplyAsync(() -> { sleep(500); return "快的"; })
).thenApply(o -> "获胜者:" + o);
fastest.thenAccept(System.out::println).join();
// 输出:获胜者:快的
四、异常处理 API 对比
| 方法 | 何时调用 | 是否改变结果类型 | 是否继续传递异常 |
|---|---|---|---|
exceptionally(Function<Throwable, T> fallback) | 仅当前阶段出错时调用 | ✅ 可返回默认值 | ❌ 被捕获,后续不再抛 |
handle(BiFunction<T, Throwable, R>) | 无论成功或失败都调用 | ✅ 可统一处理结果和异常 | ✅ 可选择重新抛出 |
whenComplete(BiConsumer<T, Throwable>) | 无论成功或失败都调用 | ❌ 不改变结果(仅消费) | ✅ 异常继续向后传播 |
🔥 关键区别:
exceptionally:只处理异常,类似catchhandle:既能处理成功也能处理失败,可做转换,类似map + catchwhenComplete:仅用于清理或日志,不能改变结果,异常会继续抛
future
.thenApply(this::process)
.handle((result, ex) -> {
if (ex != null) {
log.warn("处理失败,使用默认值", ex);
return DefaultValue;
}
return result;
});
五、执行线程模型总结(最容易踩坑!)
| 方法 | 执行线程来源 |
|---|---|
thenApply, thenAccept, thenRun | ❗ 前一个任务所在线程(可能阻塞流水线) |
thenApplyAsync 等 Async 版本 | ✅ ForkJoinPool.commonPool() 或指定 Executor |
supplyAsync() 无参 | commonPool |
supplyAsync(executor) | ✅ 指定线程池 |
⚠️ 大坑警告: 如果你在
thenApply中做了耗时操作(如 sleep、DB 查询),它会阻塞前一个任务的线程,可能导致线程饥饿。
✅ 最佳实践:
// 始终使用自定义线程池 + Async 版本
CompletableFuture.supplyAsync(() -> fetchUser(), myExecutor)
.thenApplyAsync(user -> enrichUser(user), myExecutor)
.thenAcceptAsync(user -> save(user), myExecutor);
六、API 语义速查表(一句话总结)
| API | 一句话用途 |
|---|---|
supplyAsync | 异步执行一个有返回值的任务 |
thenApply | 上一步结果拿来转换成新值(同步执行) |
thenApplyAsync | 同上,但异步执行(推荐) |
thenAccept | 拿到结果后消费(如打印、存储) |
thenRun | 不关心结果,只关心“完成”这件事 |
thenCombine | 两个异步任务都完成,合并结果 |
thenCompose | 上一步结果用于启动下一个异步任务(扁平化) |
exceptionally | 出错时提供默认值(兜底) |
handle | 成功和失败都能处理,可统一转换 |
whenComplete | 成功或失败都执行(如关闭资源、打日志) |
allOf | 所有任务完成才算完成(注意拿不到结果) |
anyOf | 任一任务完成就完成(注意返回 Object) |
✅ 总结:如何选择?
| 你要做什么 | 推荐 API |
|---|---|
| 异步获取数据 | supplyAsync(task, executor) |
| 处理结果并返回新值 | thenApplyAsync(func, executor) |
| 只消费结果(如打印) | thenAcceptAsync(consumer, executor) |
| 任务完成后发通知 | thenRunAsync(runnable, executor) |
| 合并两个独立异步结果 | thenCombine(other, combiner) |
| 异步依赖(A 结果 → B 任务) | thenCompose(func) |
| 出错返回默认值 | exceptionally(fallback) |
| 统一处理成功/失败 | handle((res, ex) -> ...) |
| 清理资源或打日志 | whenComplete((res, ex) -> ...) |
| 等待所有任务完成 | CompletableFuture.allOf(...).join() |
| 获取最快结果 | CompletableFuture.anyOf(...).join() |
🎯 三、最佳实践建议
-
✅ 始终使用自定义线程池
ExecutorService executor = Executors.newFixedThreadPool(10); CompletableFuture.supplyAsync(task, executor); -
✅ 链式调用中显式指定线程池
future.thenApplyAsync(transformer, executor); -
✅ 避免在
thenApply中做耗时同步操作- 否则会阻塞整个异步流水线
-
✅ 合理使用
timeout(需手动实现)CompletableFuture.orTimeout(future, 3, TimeUnit.SECONDS); // 或自己写:applyToEither(CompletableFuture.delayedExecutor(...)) -
✅ 监控与日志
- 记录任务开始/结束时间
- 打印线程名便于排查
🏁 总结
| 特性 | 说明 |
|---|---|
| ✅ 优势 | 简化异步编程、支持函数式链式调用、强大的组合能力 |
| ❌ 劣势 | 学习曲线陡、线程模型复杂、异常处理易忽略 |
| 🎯 适用场景 | Web 请求聚合、IO 密集型任务、微服务调用编排、数据加载优化 |
| 🚫 不适用场景 | 简单同步任务、CPU 密集型且无并行需求 |
955

被折叠的 条评论
为什么被折叠?



