应慎用executors.newfixedthreadpool,因其使用无界队列linkedblockingqueue且无法动态调参,易致oom或任务堆积;生产环境须用threadpoolexecutor手动构造,显式配置有界队列、拒绝策略及命名线程工厂。

固定大小线程池:Executors.newFixedThreadPool(int nThreads) 怎么用、为什么慎用
它返回一个 ThreadPoolExecutor,核心线程数与最大线程数相等,使用无界队列 LinkedBlockingQueue。看似简单,但实际线上容易出问题。
常见错误现象:OutOfMemoryError: unable to create new native thread 或任务长时间堆积无响应——因为队列无界,任务持续提交时内存不断增长,线程却始终不扩容。
- 只适合任务量稳定、执行时间短、可控的场景(如内部批处理管道)
-
nThreads建议设为 CPU 核心数 + 1(IO 密集可略高),避免过度竞争 - 不能通过
setCorePoolSize()动态调整,因为返回的是包装过的ExecutorService,不暴露底层ThreadPoolExecutor - 真正需要固定线程能力时,应直接 new
ThreadPoolExecutor,而非依赖工厂方法
缓存型线程池:Executors.newCachedThreadPool() 的真实行为与风险
它创建的是核心线程数为 0、最大线程数为 Integer.MAX_VALUE、空闲 60 秒回收、使用 SynchronousQueue 的线程池。名字叫“缓存”,但本质是“按需狂建 + 懒回收”。
使用场景极少:仅适用于大量极短生命周期、突发性、彼此独立的任务(比如 RPC 短连接回调)。绝大多数业务场景下它是反模式。
- 一旦任务提交速度 > 执行速度,线程数会指数级增长,迅速耗尽系统资源
-
SynchronousQueue不存储任务,意味着没有排队缓冲,所有任务必须立刻有空闲线程承接,否则就新建线程 - 60 秒空闲回收策略在流量毛刺后可能留下大量僵尸线程,GC 压力上升
- 无法限制最大并发数,也无法设置拒绝策略(默认是
AbortPolicy,但你根本来不及捕获)
替代方案:用 ThreadPoolExecutor 直接构造更可控
工厂方法只是语法糖,掩盖了关键参数。生产环境应绕过 Executors,自己构建 ThreadPoolExecutor 实例。
例如模拟“安全的固定池”:
new ThreadPoolExecutor(
4, 4,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue(1024), // 有界队列
new ThreadFactoryBuilder().setNameFormat("biz-task-%d").build(),
new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝时由调用线程执行
);
- 显式控制核心/最大线程数、队列容量、拒绝策略,三者缺一不可
- 队列建议用有界队列(如
ArrayBlockingQueue),配合合适的拒绝策略,让系统具备背压能力 - 务必设置
ThreadFactory,否则线程名全是pool-1-thread-1,排查时无法区分来源 - 不要依赖
Executors.defaultThreadFactory(),它不设守护线程、无命名、无异常处理器
为什么 Executors 工厂方法被阿里《Java 开发手册》明确禁止?
不是因为它不能用,而是它把复杂决策简化成一个参数,诱导开发者忽略线程池本质:它是有状态的、有资源边界的、需要监控和治理的组件。
最容易被忽略的一点:Executors 创建的线程池,shutdown() 后若仍有任务在队列中,JVM 无法正常退出——因为线程池未被正确 awaitTermination,且无监控手段发现残留任务。
- 线程池不是“创建即托管”,必须配合同步关闭逻辑(如 Spring 的
@PreDestroy) - 必须暴露
ThreadPoolExecutor的指标(如getActiveCount()、getQueue().size())用于 Prometheus 或日志埋点 - 所有线程池都应有明确归属(模块名、用途标签),避免多个业务共用同一池导致相互干扰










