← 返回软件技术总览 软件技术 · 知识点深化 · 异步编程与 CompletableFuture:从 Future 到链式编排
软件技术 · 知识点深化 · 并发编程

异步编程与 CompletableFuture:从 Future 到链式编排

JDK8 的 CompletableFuture 把异步编程从" future.get() 死等"解放成链式编排:thenApply 转换、thenCombine 合并、thenCompose 扁平化、exceptionally 兜底。这一页把异步回调地狱写成优雅流水线,面试手撕 CompletableFuture。

① 怎么学(4 步走,约 65 分钟)

先建立直觉再抠细节,按这四步走最稳:

1看图建立直觉(10 分钟)
读②③:先在脑子里画出本课核心结构图。
2记完整体系(15 分钟)
读④:把对比表和公式看懂,不要急着背。
3跟例题走一遍(20 分钟)
精读⑤:看三个例题怎么用知识点解题。
4刷题纠错(剩余时间)
做⑦⑩,错题回⑥诊断。
本课小目标学完你要能:① 对比 Future 与 CompletableFuture;② 写出链式编排代码;③ 区分 thenApply/thenCompose/thenCombine;④ 处理异步异常。

② 一图看懂:CompletableFuture 方法全景

CompletableFuture 创建 supplyAsync/runAsync 转换 thenApply 同步链式 组合 thenCombine 合并两个 异常 exceptionally/handle 应用:微服务聚合/并行调用 订单页查多服务 易错:get() 阻塞失去异步意义 应该用 thenApply 回调
读法:中心是本课主题,四条分支展开核心维度,下方是典型应用与高频易错点。

③ 本质直觉:CompletableFuture 就是"外卖流水线"

传统 Future 像点外卖后一直盯着手机等(future.get() 阻塞)。CompletableFuture 像餐厅后厨流水线:切菜→炒菜→装盘→上菜,每一步做完自动交给下一步,不用人盯着。

supplyAsync:开一个异步任务去做菜。
thenApply:菜做好后加工(转换结果)。
thenCombine:两个菜都做好后拼成套餐。
thenCompose:第一个菜做好后,用它去点第二个菜(嵌套拍平)。
exceptionally:菜糊了换个方案。

同步 vs 异步版本:thenApply 用调用者线程执行;thenApplyAsync 丢给线程池。默认用 ForkJoinPool.commonPool()。

supplyAsync thenApply转换 thenCombine合并 exceptionally兜底
Future 的局限Future.get() 阻塞、不能链式调用、不能组合多个 future、异常处理麻烦。CompletableFuture 全部解决。

④ 完整知识体系

核心方法分类

方法作用类比
supplyAsync异步有返回值下单做菜
runAsync异步无返回值叫号
thenApply同步转换结果加工菜
thenApplyAsync异步转换丢给别的档口
thenCombine合并两个 future两个菜拼套餐
thenCompose嵌套 future 拍平用第一道菜再点第二道
exceptionally异常兜底菜糊了换菜
allOf等全部完成等所有菜齐
thenApply vs thenComposethenApply 是 T->R;thenCompose 是 T->Future,拍平嵌套
allOf 用法CompletableFuture.allOf(f1,f2,f3).join() 等全部完成

异常处理

exceptionally:出异常时返回默认值。
handle:无论正常还是异常都执行,能拿到结果或异常。
whenComplete:消费结果但不改变结果。

线程池建议默认 ForkJoinPool.commonPool() 适合 CPU 密集;IO 密集(调远程服务)必须传自定义线程池,否则公共池被阻塞打满。

⑤ 应用场景与例题

例1 并行查用户信息和订单
两个服务并行调用,总耗时=max(两个)。
CompletableFuture f1 = supplyAsync(()->userService.get(id)); CompletableFuture f2 = supplyAsync(()->orderService.get(id)); 用 thenCombine 合并结果,总耗时≈较慢那个,而非两者之和。
例2 thenApply 和 thenCompose 区别
为什么需要 thenCompose?
thenApply 返回值会被自动包装成 CompletableFuture,嵌套两层。thenCompose 像 flatMap,把嵌套拍平。如果第二步本身返回 CompletableFuture,用 thenCompose。
例3 allOf 等三个任务完成
三个并行任务都完成后汇总。
CompletableFuture.allOf(f1,f2,f3).join(); 然后用 f1.get() 等取结果。注意 allOf 本身不返回结果,要手动取。
做题心法链式调用的关键:每一步返回新的 CompletableFuture,像流水线一样串起来。

⑥ 高频错误诊断(4 条)

错误1:异步任务里调用 get()get() 阻塞,失去异步意义。用 thenApply 回调。
错误2:用默认线程池跑 IO 任务commonPool 被阻塞 IO 打满,必须传自定义线程池。
错误3:thenApply 和 thenCompose 混用返回 CompletableFuture 的函数必须用 thenCompose,否则嵌套。
错误4:异常没人处理异步任务异常会被吞掉,必须 exceptionally 或 handle 兜底。

⑦ 考点真题演练(5 题)

考点分布

考法出题形式应对
方法问 thenApply vs thenCompose后者拍平嵌套
创建问 supplyAsync 作用异步有返回值
合并问 allOf 语义等全部完成
线程池问为什么要传自定义池IO 阻塞 commonPool

真题basic1. CompletableFuture.supplyAsync 的作用是?

真题mid2. thenCompose 相对于 thenApply 类似 Stream 的哪个操作?

真题mid3. CompletableFuture.allOf(f1,f2) 的作用是?

真题hard4. IO 密集异步任务为什么要传自定义线程池?

真题hard5. 异步任务异常默认会怎样?

⑧ 必背知识点卡

创建:supplyAsync 有返回,runAsync 无返回
转换:thenApply 同步,thenApplyAsync 异步
拍平:thenCompose = flatMap
合并:thenCombine 两个 future
全等:allOf 等全部,anyOf 等任一
异常:exceptionally 兜底,handle 都处理
线程池:IO 任务必须传自定义池 别用 commonPool

⑨ 动手输出:用 CompletableFuture 优化订单详情页

场景:订单页要查用户、商品、库存、优惠券 4 个服务,串行要 800ms。
① 现状:4 个服务串行调用,总耗时=4×200=800ms。
② 改造:用 supplyAsync 并行发起 4 个请求,各占一个线程。
③ 合并:allOf 等 4 个都完成,然后汇总成页面 VO。
④ 耗时:≈ max(200ms)=200ms,快 4 倍。
⑤ 兜底:任何一个服务挂了,exceptionally 返回降级数据(如用户信息查不到用默认头像)。
口述思路"能并行就别串行,CompletableFuture 让并行像写同步代码一样自然。"

⑩ 分层练习(基础 + 中档 + 拔高)

▍基础 6 题

基础1CompletableFuture 哪个方法异步有返回值?
supplyAsync。
基础2thenApply 和 thenApplyAsync 区别?
前者同步执行,后者丢线程池。
基础3allOf 的作用?
等所有 future 完成。
基础4异常用哪个方法兜底?
exceptionally。
基础5thenCompose 类似 Stream 的什么?
flatMap。
基础6默认线程池是哪个?
ForkJoinPool.commonPool()。

▍中档 5 题

中档1Future 的主要缺点?
get 阻塞、不能链式、不能组合、异常难处理。
中档2thenCombine 和 thenCompose 区别?
Combine 合并两个独立 future;Compose 是嵌套 future 拍平。
中档3为什么 IO 任务不能用 commonPool?
commonPool 线程数=cpu核数,IO 阻塞会占满。
中档4handle 和 exceptionally 区别?
handle 正常异常都处理;exceptionally 只处理异常。
中档5whenComplete 的作用?
消费结果但不改变结果,类似 peek。

▍拔高 5 题

拔高1allOf 为什么不直接返回结果?
它是 Void,需要手动 get 各个 future。
拔高2anyOf 适合什么场景?
多个冗余服务调用,谁先回用谁。
拔高3CompletableFuture 线程不结束怎么办?
非守护线程会阻塞 JVM 退出,用 commonPool 守护线程避免。
拔高4怎么设置异步任务超时?
orTimeout() 或 get(timeout, unit)。
拔高5thenApply 用哪个线程执行?
默认由完成它的线程执行(可能是调用线程或异步线程)。

⑪ 记忆口诀 + 7 天复习计划

三句口诀① supplyAsync 开任务,thenApply 串转换。② thenCompose 拍平,thenCombine 合并。③ IO 必须传线程池,异常兜底 exceptionally。
天任务自检
第 1 天读②③,画方法分类图能分类
第 2 天背方法表 + 基础 6 题方法名默写
第 3 天做中档 5 题,写并行调用代码代码能跑
第 4 天做拔高 5 题,加超时和降级完整可用
第 5 天做⑦真题 5 题限时每题 2 分钟
第 6-7 天合书口述 Future 缺点不看资料

← 返回软件技术总览