默认使用 forkjoinpool.commonpool() 会导致 cpu 密集型任务阻塞整个异步链路;应显式传入自定义线程池,并按 io/混合型任务合理设置线程数;用 handle() 替代 thenapply() 实现异常兜底,allof 需配合 handle() 或 getnow() 处理个别失败;重试需独立封装并防无限循环;错误传播须分类型处理。

CompletableFuture.supplyAsync 启动异步任务时,线程池不传参会踩什么坑
默认用 ForkJoinPool.commonPool(),但它的并行度等于 CPU 核数,一旦有 CPU 密集型任务卡住,后续所有异步任务都会排队等待,甚至拖垮整个应用的异步链路。
- 显式传入自定义线程池,比如
Executors.newFixedThreadPool(10)或更稳妥的new ThreadPoolExecutor(...) - IO 密集型任务建议线程数设为 2×CPU 核数;混合型任务建议按实际压测结果调优
- 别在
supplyAsync里直接 new Thread,会导致线程失控、GC 压力大、无法统一管理
多个 CompletableFuture 如何串成一条链,且前一个失败不影响后一个执行
用 handle() 而不是 thenApply():前者接收正常结果和异常两个参数,后者只处理成功路径,一出错整条链就中断。
-
thenApply():适合“必须上一步成功才继续”的场景,例如解析 JSON 后再存 DB -
handle((result, ex) -> { if (ex != null) return fallbackValue; else return process(result); }):兜底更可控 - 如果想“无论成败都触发下一步”,用
whenComplete(),但它不改变返回值类型,不能用于变换结果
如何等全部 CompletableFuture 完成,但不因某个失败而整体失败
CompletableFuture.allOf() 本身不聚合结果,且任意一个异常就会让 join() 抛出 CompletionException,必须手动包装。
- 先用
allOf(c1, c2, c3).join()等全部结束(包括异常) - 再分别对每个
CompletableFuture调用getNow(null)或handle()提取结果或异常信息 - 更简洁的做法是用
collect(Collectors.toList())批量转成 list,再流式处理每个 future 的状态
链式编排中,怎么把上一步的异常传递给下游做定制化重试
原生 CompletableFuture 不支持自动重试,得靠 handle() + 递归调用或循环封装实现,且要防无限重试。
- 在
handle()里判断ex instanceof TimeoutException或 HTTP 5xx,再决定是否重试 - 重试逻辑别写在 lambda 里,抽成独立方法,方便加计数、退避(如
Thread.sleep(100 * retryCount))和日志 - 注意:用
supplyAsync重试时,新任务仍走同一套线程池,若线程池已满,重试会直接被拒绝,得配合拒绝策略或缓冲队列
链式编排真正难的不是语法,而是错误传播路径的设计——同一个 handle() 方法里混着空指针、超时、业务码校验失败,它们该走不同降级分支,而不是全塞进一个 if (ex != null)。











