必须为completablefuture显式指定线程池:io任务用固定大小有界队列线程池,cpu任务用可控并行度的forkjoinpool,关键业务独占线程池,回调阶段也需指定线程池,且所有异步方法禁用默认commonpool。

CompletableFuture本身不执行任务,它只负责编排和组合;真正干活的是线程池。用错线程池,再优雅的链式调用也会在生产环境崩盘。
别再依赖ForkJoinPool.commonPool()
supplyAsync()不传Executor时,默认走ForkJoinPool.commonPool(),这个池子线程数≈CPU核心数−1,专为短时计算任务设计。但IO操作(如HTTP调用、数据库查询)一多,线程全被阻塞住,后续任务排队等待,响应时间飙升甚至超时。
- 公共池被多个模块共享,一个慢查询可能拖垮整个服务的异步逻辑
- 无界队列+固定线程数 → 内存持续增长,最终OOM
- 线程名全是ForkJoinPool.commonPool-worker-N,出问题时日志无法定位业务归属
按任务类型配专属线程池
IO密集型任务(网络、DB、文件读写)需要更多线程来“掩盖”阻塞等待;CPU密集型则应控制并发数,避免上下文切换开销。混合场景必须隔离。
- IO型:用FixedThreadPool或自定义ThreadPoolExecutor,核心线程数设为2×CPU核心数起步,配合有界队列(如ArrayBlockingQueue)和CallerRunsPolicy防雪崩
- CPU型:可继续用ForkJoinPool,但建议显式构造并设置parallelism,不共用commonPool
- 关键业务(如支付、风控)必须独占线程池,避免被日志、监控等低优任务干扰
回调阶段也要指定线程池
thenApply、thenAccept这类同步回调,执行线程取决于前序Future完成时所在线程——容易意外串入IO线程或主线程,造成阻塞或资源争抢。用thenApplyAsync、thenAcceptAsync并传入对应线程池,才能确保调度可控。
- 例如:从DB查数据用ioPool,后续JSON解析用cpuPool,就该拆成supplyAsync(..., ioPool).thenApplyAsync(..., cpuPool)
- 避免在回调里做耗时操作(如同步HTTP请求、复杂计算),否则会卡住整个线程池
- 所有异步方法都显式传Executor,不依赖默认行为,代码才具备可维护性和可预测性
线程池要可观测、可治理
光配对参数不够,得让线程池“说话”。命名、监控、动态调整缺一不可。
- 用ThreadFactoryBuilder.setNameFormat("order-io-%d")统一命名,日志中一眼识别来源
- 暴露ThreadPoolExecutor的getActiveCount()、getQueue().size()等指标到Prometheus,设置告警阈值
- 核心线程池参数(如队列容量、最大线程数)支持配置中心动态刷新,大促前可快速扩容
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











