CompletableFuture 和 Future 有什么区别?异步任务如何组合、处理异常?
下面是一段教学用的模拟面试。
🧑💻 面试官:Future 和 CompletableFuture 有什么区别?
🙋♂️ 我:Future 通过 get 拿结果,CompletableFuture 可以异步执行。
🧑💻 面试官:Future 对应的任务也可以异步执行。那查询用户以后再查余额,以及同时查价格、库存,这两种关系怎么表示?
🙋♂️ 我:给任务注册后续处理,组合它们的结果。
🧑💻 面试官:后续处理一定换线程吗?超时以后,原来的查询一定停了吗?
这道题真正要讲的是「任务之间的关系」。可以编排后续任务,不等于每一步都有新线程,也不等于超时会终止工作。
面试速答(60 秒版)
Future 表示一个稍后可获得的计算结果,可以查询状态、尝试取消,也可以通过 get 等待结果。它本身也能对应异步任务,不是只能同步执行。
CompletableFuture 在此基础上实现了 CompletionStage,可以注册结果转换、依赖任务、多个任务的合并,以及异常处理,不必每一步都先阻塞拿结果。
例如 thenApply 转换一个结果,thenCompose 接上一个返回新阶段的任务,thenCombine 合并两个结果。是否换线程,要看是不是 Async 方法以及实际 Executor;不带 Async 的后续阶段可能由完成前一阶段的线程执行。
实际使用还要明确线程池、超时和降级边界。Future 超时或异常完成,并不天然代表底层网络请求已经取消,也不能把 allOf 当作直接返回所有结果的列表。

知识点详解:先画出依赖关系,再选择 API
领取结果,和安排结果出来以后做什么
咱们假设首页需要显示用户余额。后台先查询用户,拿到用户 ID 以后,再调用余额服务。
只用 Future,也能提交查询任务,再通过 get 取得结果。但是如果当前线程马上 get,它仍然需要等待。异步执行的任务,不代表调用者从来不阻塞。
CompletableFuture 可以先登记:用户结果出来以后,启动余额查询。当前代码描述的是依赖,真正的下一步在前一个阶段完成后再执行。
它也可以被手动 complete,因此不只是一层线程池包装。要区分“代表结果的阶段”和“底层正在运行的具体任务”。
thenApply、thenCompose、thenCombine,分别解决什么关系?
这三个名字可以放到同一个例子里理解:
| 需求 | 对应关系 | 常用方法 |
|---|---|---|
| 查询到用户,把姓名转成显示文本 | 一个结果变成另一个普通值 | thenApply |
| 查到用户,再发起异步余额查询 | 后一任务依赖前一结果 | thenCompose |
| 价格和库存都查好了,组成商品信息 | 两个结果合并 | thenCombine |
关键区别在回调返回什么。余额查询返回另一个 CompletableFuture,如果用 thenApply,就会产生嵌套阶段;thenCompose 会把这个依赖接成一条阶段链。
价格与库存没有前后依赖,可以分别启动,再组合。不能为了“用了异步框架”,把本来独立的两个查询写成必须先后完成。
allOf 可以等待一组阶段整体完成,但它自己的结果类型是 Void。需要结果时,仍要在适当的完成边界后从各个阶段取得,不能当成自动汇总结果数组的 API。

没写 Async,不代表偷偷开了新线程
不带 Async 的后续阶段,可能在完成前一个阶段的线程中执行,也可能由其他参与完成的调用线程执行。不能把业务规则建立在“它固定运行于某个线程”上。
Async 方法没有显式传入 Executor 时,通常使用默认异步执行器,例如 commonPool;具体 API 还有默认执行器的约定。传入明确 Executor,才更容易控制资源归属。
假设查询数据库是阻塞操作,直接塞进共享线程池,可能影响其他任务。项目中需要结合连接池容量、线程池队列、拒绝策略和请求超时安排,而不是统一写一个 Async 后缀就算完成并发设计。
一个阶段失败了,后面怎样处理?
如果查询用户异常完成,只处理正常结果的后续转换通常不会执行,异常会沿阶段链传播。
exceptionally 可以在失败时返回替代值;handle 可以同时处理成功结果或异常,并产生新结果;whenComplete 主要用于观察完成情况,比如记录日志,不应该被理解为无条件恢复失败。
降级值也要有意义。余额查询失败后直接返回 0,可能让用户以为真的没有余额。更合理的处理可能是“暂时无法查询”,而不是为了让链条成功就抹掉错误。
orTimeout 让阶段在超时后异常完成,不等于原查询一定停止。CompletableFuture 的取消也不能简单理解成强制中断所有底层工作。需要取消时,还要把取消信号传到真实请求或执行任务。

面试官继续追问
异步链的最后能不能 join?
可以,但它会等待未完成结果。要看当前线程能否阻塞,尤其不要在有限线程池里让任务互相等待,耗尽执行其他任务的机会。
为什么线上失败多了,但监控显示“成功”?
可能把所有异常都转换成默认结果,又只统计最终阶段成功。应分别统计原操作失败、是否发生降级,以及最终响应,不能让恢复逻辑掩盖故障。
方法与执行策略依据 Java 25 CompletableFuture API 核验。
面试速记卡
- Future:表示未来结果,可以等待、查询和尝试取消。
- CompletableFuture:还能登记依赖、转换、合并和异常处理。
- thenApply / thenCompose:普通值转换与阶段连接,不要混淆。
- 执行线程:看 Async 与 Executor,不是每一步都换线程。
- allOf:等待整体完成,不直接返回结果列表。
- 超时与取消:阶段状态改变,不等于底层工作已经停止。
公司面试真题
这道题暂未收录可核验的公司真题来源。你可以先阅读本文解析,或浏览已收录的公司面试真题。
浏览公司面试真题 →