结论:supplyasync + thencompose/thencombine + exceptionally 可覆盖大多数异步编排需求,但关键陷阱是线程池选错、异常被吞、结果类型不匹配;thencompose用于下游任务返回completablefuture需扁平化,thencombine合并两个已知cf结果,allof仅等待完成不合并值,exceptionally仅捕获上游未处理异常,自定义线程池为必选项。

直接说结论:用 supplyAsync + thenCompose/thenCombine + exceptionally 三类方法组合,就能覆盖绝大多数异步编排需求;但关键不在“会写”,而在“线程池选错”“异常被吞”“结果类型不匹配”这三处容易静默失败。
什么时候必须用 thenCompose 而不是 thenApply
当你下一个异步任务依赖上一个任务的返回值,并且它本身也返回 CompletableFuture 时,thenApply 会套娃出 CompletableFuture<completablefuture>></completablefuture>,而 thenCompose 才能扁平化。
- 错误写法:
future.thenApply(result -> fetchUserDetailAsync(result.id))→ 返回的是CompletableFuture<completablefuture>></completablefuture> - 正确写法:
future.thenCompose(result -> fetchUserDetailAsync(result.id))→ 返回CompletableFuture<userdetail></userdetail> - 典型场景:查用户 ID 后再异步查其订单列表、查商品 ID 后再异步查库存和价格
thenCombine 和 allOf 的本质区别在哪
thenCombine 是「等两个已知的 CompletableFuture 都完成,再用它们的结果做一次计算」;allOf 只是「等一堆 CompletableFuture 全部完成」,不合并结果,也不处理类型,必须手动 join() 拿值。
-
thenCombine(a, b, (x, y) -> x + y):a 和 b 类型可不同,返回值由 lambda 决定 -
CompletableFuture.allOf(a, b, c).thenRun(...):无法直接拿到 a/b/c 的结果,得写a.join()、b.join()—— 且一旦某个失败,join()就抛异常 - 性能提示:如果只是要“全部完成后再通知”,用
allOf;如果要“取结果做聚合计算”,优先用thenCombine或thenAcceptBoth
为什么 exceptionally 经常不生效
因为 exceptionally 只捕获上游阶段抛出的异常,不处理自己 lambda 里新抛的异常;而且它只对「未被其他回调处理过的异常」起作用。
- 常见陷阱:在
thenApply里手动 throw new RuntimeException("xxx") → 这个异常不会进exceptionally,而是直接中断链,除非你后面再接一个exceptionally - 更稳的做法:用
handle,它既能读到正常结果,也能读到异常对象,还能返回任意类型(包括兜底默认值) - 线程池影响:如果用了自定义线程池,但没设置
UncaughtExceptionHandler,底层异常可能直接被吞掉,日志都看不到
自定义线程池不是可选项,是必选项
ForkJoinPool.commonPool() 默认大小是 CPU 核心数 -1,IO 密集型任务(比如 HTTP 调用)用它,很快就会卡死——所有异步任务排队等那几个线程,反而比同步还慢。
- 推荐配置:
Executors.newFixedThreadPool(20)(根据 QPS 和平均 RT 估算,一般设为 4×平均并发请求数) - 必须传入线程池的场景:
supplyAsync(() -> ..., executor)、thenApplyAsync、whenCompleteAsync—— 带Async后缀的方法才走线程池,不带的都在上游线程执行 - 容易忽略的一点:即使主线程是 Web 容器线程(如 Tomcat 的
http-nio-8080-exec-5),thenAccept这种不带 Async 的回调,也会在这个容器线程里跑,可能拖慢整个请求处理链
最常被跳过的细节是:多个 CompletableFuture 组合后,异常传播路径和线程上下文(比如 MDC 日志跟踪 ID)会断裂。别只盯着功能是否跑通,得在每个 then 阶段手动透传上下文,或统一用支持上下文继承的线程池装饰器。










