一个首页接口要并行调用商品、库存、营销和推荐服务,你会怎么用 CompletableFuture 编排?
先说结论:CompletableFuture 用阶段组成异步数据流:thenApply 转换结果,thenCompose 串联返回 Future 的依赖任务,thenCombine 合并两个独立结果。生产环境应传入隔离的线程池,并为外部调用设置超时和降级。
我先给结论,再说明它在项目里解决什么问题。掌握异步转换、任务组合、异常处理和线程池隔离。CompletableFuture 用阶段组成异步数据流:thenApply 转换结果,thenCompose 串联返回 Future 的依赖任务,thenCombine 合并两个独立结果。生产环境应传入隔离的线程池,并为外部调用设置超时和降级。 不带 Async 的后续阶段可能由完成前一阶段的线程执行;带 Async 的版本会提交到指定执行器,二者影响线程上下文和调度开销。
核心机制我会按一次真实执行过程来讲。沿着「提交异步阶段 → 在线程池执行任务 → 组合依赖与汇聚 → 传播结果或异常 → 设置超时并释放资源」观察输入、状态与输出,这些阶段都可以从日志、指标或源码里验证。 提交异步阶段 supplyAsync/runAsync 决定初始阶段与执行器,默认 commonPool 不适合混入不可控阻塞 I/O。 在线程池执行任务 每个阶段在指定或继承的执行器中运行,thenApply 与 thenApplyAsync 的调度语义不同。
实现细节只抓关键入口,不会整段背源码。CompletableFuture#uniApply/uniCompose:阶段完成与依赖触发。 ForkJoinPool.commonPool:默认异步执行器及阻塞风险。 我会先确认请求实际走到了哪条路径,再用运行数据验证,不会只看类名或配置猜测。
放到生产使用时,我会关注参数和验证数据。注入一个 2s 下游,验证 Future 超时后底层连接是否仍占用;记录各阶段线程名、队列和取消结果。 固定输入和基线 先在没有故障注入的环境执行上述配置,固定数据规模、并发度、运行时版本和预热时间。以「阶段 P99」为主基线,记录值应满足「各自受子预算约束」;同时保存 各阶段耗时、执行器队列长度,使后续变化能够回到同一时间轴比较。
最后补充常见误区和使用边界。接口对 CompletableFuture 设置 300ms 超时后返回,但 JDBC 查询不可中断,后台任务继续占用连接,流量高峰时连接池耗尽。修复同时设置数据库查询超时、独立有界执行器和并发许可,并对取消无效的任务做观测。 默认 commonPool 被阻塞任务占满:各阶段耗时:独立有界执行器。 方案:更适合的场景:主要收益:代价与边界。 同步调用:依赖少且延迟预算充足:控制流直观、异常清晰:串行累加等待时间。 CompletableFuture:多个独立 I/O 可并行且需组合:编排表达力强:执行器、异常和取消复杂。 响应式流:长链路流式数据与背压:端到端非阻塞和流控:学习与调试成本高。 选型至少带上 并发线程数、临界区长度、阻塞比例和任务到达速率,并用上面的量化基线验证;未知数据应明确为待测假设。