关键在于理清“谁在哪执行、结果怎么传、错在哪兜住”:completablefuture仅编排,线程池执行;带async方法才换线程且须显式指定专用池(禁用commonpool);按语义选thenapply/thencompose/thencombine;io/cpu任务分池;异常与超时须主动配置。

关键不在“怎么写链”,而在于“谁在哪执行、结果怎么传、错在哪兜住”。CompletableFuture 本身不干活,只编排;线程池才是真正在跑任务的执行引擎。
每个阶段必须明确线程归属
不带 Async 后缀的方法(如 thenApply、whenComplete)默认在上一阶段完成的线程里执行——容易串行阻塞,尤其不适合 I/O 或耗时计算。
带 Async 后缀的方法(如 thenApplyAsync、thenComposeAsync)才真正换线程,但必须显式传入线程池,绝不能依赖 ForkJoinPool.commonPool()。
- 查用户 ID 后再查订单:用
thenComposeAsync(…, ioPool),避免卡在同一线程 - 纯内存转换(比如 JSON 解析后转 DTO):可用
thenApply,轻量无切换开销 - 日志记录或埋点这类副作用操作:推荐
whenCompleteAsync(…, logPool),隔离主链
按依赖关系选对编排方法
不是所有 “then” 都等价。选错语义,轻则类型报错,重则逻辑阻塞或嵌套套娃。
- 单值链式加工(A → B → C,每步只用上一步结果):用
thenApply或thenApplyAsync - 异步嵌套触发(A 结果是 B 的输入,B 本身也是
CompletableFuture):必须用thenCompose或thenComposeAsync - 并行任务合并(两个独立异步任务完成后一起算):用
thenCombine或thenCombineAsync
反模式:thenApply(u -> fetchOrders(u.id)) 返回的是 CompletableFuture<completablefuture>></completablefuture>;正解是 thenCompose(u -> fetchOrders(u.id)),自动展平为单层 CompletableFuture<list></list>。
线程池必须分场景定制
ForkJoinPool.commonPool() 是 JVM 全局共享池,线程数 ≈ CPU 核数。一次慢 HTTP 调用就可能拖垮整个应用的异步链。
- I/O 密集型任务(HTTP、DB、文件读写):单独配
ioPool,线程数可设为 20–100,根据并发量调优 - CPU 密集型任务(加解密、图像处理、复杂计算):配
cpuPool,线程数建议设为Runtime.getRuntime().availableProcessors() - 日志/监控等副作用操作:配低优先级的
logPool,避免干扰主业务线程
异常与超时必须主动控制
CompletableFuture 不会自动吞异常,但也不会自动中断流水线或设超时——这些都得手动配。
- 用
exceptionally()处理异常并返回兜底值,但注意它不会中断后续链 - 想彻底中断,改用
handle()显式返回null或抛新异常 - 超时控制不能靠
get(timeout, unit),应在链中嵌入orTimeout()或用completeOnTimeout() - 组合多个任务时,避免用
allOf().join(),推荐thenCombine()+ 单独超时配置,防止异常被静默丢弃
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











