该选supplyasync()还是runasync()取决于是否需要返回值:需结果用supplyasync()(返回completablefuture),纯执行用runasync()(返回completablefuture);io密集型任务优先supplyasync(),火种型操作用runasync()更语义清晰。

CompletableFuture.runAsync() 和 supplyAsync() 到底该选哪个
看返回值:要结果就用 supplyAsync(),它返回 CompletableFuture<t></t>;纯执行不关心返回值,用 runAsync()。很多人一上来就写 runAsync(),后面想链式取结果时才发现没得取——因为它的返回类型是 CompletableFuture<void></void>,实际是 CompletableFuture> 且无法安全 cast。
常见错误现象:runAsync(() -> doSomething()).thenApply(x -> x.toString()) 编译失败,报错 incompatible types: cannot infer type-variable(s) U。
- IO 密集型任务(如调用 HTTP 接口、读文件)优先用
supplyAsync(),方便后续thenCompose()或thenAccept() - 如果只是发通知、打日志、清缓存这类“火种型”操作,
runAsync()更语义清晰 - 两者默认都走
ForkJoinPool.commonPool(),高并发下容易挤占 CPU 密集任务资源,生产环境建议显式传入自定义线程池
thenCompose() vs thenCombine():流水线分叉合并怎么选
关键区别在依赖关系:thenCompose() 是“串行依赖”,前一个 future 的结果是后一个 future 的输入;thenCombine() 是“并行汇合”,两个独立 future 都完成之后,用它们的结果一起算第三个值。
典型误用:把两个无关的异步请求写成 f1.thenCompose(r1 -> f2.thenApply(r2 -> r1 + r2)),这会让 f2 等待 f1 完成才启动,白白增加总耗时。
- 需要“查用户 → 查订单 → 合并渲染”这种强依赖链,用
thenCompose() - 需要“查用户信息”和“查用户权限”两个独立请求完成后拼装视图,用
thenCombine() -
thenCompose()内部必须返回一个新的CompletableFuture;thenCombine()第二个参数是普通函数,不许再返回 future
exceptionally() 和 handle() 处理异常时的隐蔽差异
exceptionally() 只捕获上游抛出的异常,且只能返回 fallback 值(类型需与原始 future 一致);handle() 是万能兜底,无论成功还是异常都会进,而且可以返回不同类型(比如异常时返回空对象,成功时返回业务实体)。
容易踩的坑:用 exceptionally() 想记录日志+返回默认值,结果发现日志没打——因为 exceptionally() 不接收正常结果,你得额外加 whenComplete() 才能覆盖全路径。
- 只做简单降级(如“查不到用户就返回 Guest”),用
exceptionally()更轻量 - 需要统一打日志、埋点、或根据 success/failed 分支走不同逻辑,直接上
handle() -
handle()里别 throw 新异常,否则下游又得套一层 handle,容易嵌套失控
join() 和 get() 在 WebFlux 或 Servlet 3.0+ 环境里千万别混用
join() 不抛受检异常,get() 抛 ExecutionException 和 InterruptedException;但更关键的是:在 Netty 或 Tomcat NIO 线程上直接调用这两个方法会阻塞事件循环线程,导致整个服务吞吐暴跌。
现象:压测时 QPS 上不去,线程堆栈里大量 CompletableFuture.join 卡在 LockSupport.park。
- Spring WebFlux 默认线程模型下,所有
join()/get()都属于反模式,必须用thenApply()、thenAccept()这类非阻塞回调 - Servlet 容器(如 Tomcat)启用异步支持后,也应避免在
AsyncContext.start()外部调用阻塞方法 - 真要同步等(比如单元测试),确保线程池不是
commonPool(),且 timeout 设置合理,避免死等
CompletableFuture 的流水线编排能力很强,但它的“非阻塞契约”很硬——一旦在不该阻塞的地方用了阻塞调用,问题不会立刻暴露,而是在高并发或慢依赖场景下突然恶化。










