Java CompletableFuture
CompletableFuture 如何组合异步任务、传递异常与配置执行器:避免阻塞式回调地狱。
#concept
#growing
#dev
#java
[!info] related notes
Java CompletableFuture
这个概念为什么出现
Future.get() 会阻塞;多依赖组装易成回调泥潭。CompletableFuture 提供声明式异步组合:thenApply/thenCompose/exceptionally 等,把任务图表达清楚。
[!abstract] 一句话理解 CompletableFuture 是可手动完成、可组合的异步结果;需要异步调度时明确 Executor,并统一处理异常与超时;并非每个阶段都必须异步执行。
最小可运行示例
场景:并行查库存与价格,再汇总下单视图
import java.util.concurrent.*;
public class OrderViewAsync {
public static void main(String[] args) throws Exception {
ExecutorService pool = Executors.newFixedThreadPool(2);
try {
CompletableFuture<Integer> stock = CompletableFuture.supplyAsync(() -> 3, pool);
CompletableFuture<Integer> price = CompletableFuture.supplyAsync(() -> 100, pool);
CompletableFuture<String> view = stock.thenCombineAsync(price,
(s, p) -> "stock=" + s + ",price=" + p, pool);
System.out.println(view.get(1, TimeUnit.SECONDS));
} finally {
pool.shutdown();
}
}
}
建议运行:
javac OrderViewAsync.java && java OrderViewAsync
期望输出:
stock=3,price=100
结合场景再看三个关注点
- supplyAsync 指定 pool,别默认打满 commonPool。
- thenCombine 表达汇合。
- get 设超时限制主线程本次等待,不会取消 stock/price/view 或底层操作。
核心概念与准确模型
1. 创建
supplyAsync/runAsync/completedFuture
2. 组合
thenApply变换thenCompose扁平嵌套异步thenCombine两路汇合- allOf 等全部输入完成(包括异常完成),返回 Void 阶段,不直接收集值;它不是快速失败或自动取消工具。
- anyOf 采用最先完成的结果或异常,不等于“第一个成功结果”,也不自动取消其他输入。\
3. 异常
- exceptionally 可恢复异常结果;handle 变换成功或失败;whenComplete 主要观察结果,通常保留原结果(回调自身抛错仍可能影响返回阶段)。\
- 异常完成时 get 通常抛 ExecutionException,join 通常抛 CompletionException;取消会抛 CancellationException。\
4. 超时
- Java 9+ 的 orTimeout/completeOnTimeout 返回并可能完成当前 CompletableFuture:前者异常完成,后者给默认值;都不保证停止底层任务。\
边界情况与反直觉行为
- 非 Async 阶段可能由完成上游的线程执行,若上游已完成也可能在注册阶段的调用线程执行;Async 使用指定 Executor,未指定时通常用 commonPool。\
- 阻塞式调用占用 commonPool 可能拖慢其他共享任务;不能只靠 Async 后缀假设任务隔离。\
- CompletableFuture.cancel(true) 不会用中断控制底层计算,mayInterruptIfRunning 在此无效;需另行管理任务、请求或资源的取消。\
常见误区
[!warning] 常见误区:在 thenApply 里做重阻塞 IO 还不换池 IO 用专用 Executor 或虚拟线程策略。
工程实践
- 每条业务异步边界明确线程池。
- 统一包装异常为领域错误。
- 指标记录耗时与超时。
- 与响应式库边界分清,不混风格。
本节总结
- CF=异步组合工具
- Executor + 超时 + 异常
- 汇合与扁平化是核心技能
自测题
- thenApply 与 thenCompose 差在哪?
- 为什么要避免默认 commonPool 跑阻塞任务?
参考答案
- apply 映射值;compose 用于返回另一个 CompletionStage 并扁平化。
- commonPool 被阻塞会拖垮其它并行任务。
延伸阅读与资料来源
| 资料 | 类型 | 支撑内容 |
|---|---|---|
| CompletableFuture API | API | 组合方法 |