阿里禁止使用Executors创建线程池,因其默认的无界队列(LinkedBlockingQueue容量为Integer.MAX_VALUE)会导致任务持续堆积、内存耗尽并引发OOM;同时CachedThreadPool等允许线程数达Integer.MAX_VALUE,易致资源耗尽。

Executors.newFixedThreadPool 创建的线程池,表面看是“固定线程数”,但真正决定它行为上限的,其实是背后那个默认的 LinkedBlockingQueue——它不是“大”,而是“几乎无界”。
默认构造即无界:Integer.MAX_VALUE 不等于无限,但足够危险
LinkedBlockingQueue 无参构造器源码就是:new LinkedBlockingQueue<Runnable>(),内部等价于 new LinkedBlockingQueue<Runnable>(Integer.MAX_VALUE)。这不是理论上的无限,而是约 21 亿个任务容量。在实际业务中,只要任务提交速度持续高于处理速度,队列就会不断膨胀,内存迟早被吃光。
- 比如线程池大小为 4,每个任务平均耗时 1 秒,但每毫秒提交 10 个任务 → 每秒积压 9990 个 → 2 分钟后队列就超百万
- JVM 堆内存若设为 1GB,存几百万 Runnable 实例(含闭包、上下文对象)极易触发 OOM
无界 ≠ 可靠:它不拒绝,也不限流,只默默堆积
有界队列(如 ArrayBlockingQueue)会在满时触发拒绝策略(如抛异常、丢弃、调用者运行),而 LinkedBlockingQueue 的 put() 方法会一直阻塞,offer() 在无界下永远返回 true。这意味着:系统不会主动告诉你“我撑不住了”,只会安静地走向崩溃。
- FixedThreadPool 的拒绝策略(如 AbortPolicy)根本不会被触发——因为队列永不“满”
- 监控指标里可能只看到队列 size 持续上涨,CPU 和线程数却稳定,误判为“系统健康”
为什么设计成无界?历史权衡与现实陷阱
早期 Java 并发库倾向于“先接住再处理”,避免因队列满导致上游调用失败。但这个设计假设了任务执行足够快、提交节奏可控。如今微服务高频调用、异步日志、批量导入等场景,让“提交远快于消费”成为常态。
- 它适合低频、稳态、可预测的任务(如后台定时清理)
- 不适合流量突增、IO 阻塞长、或上游不可控的场景(如 Web 请求转异步处理)
怎么安全用?别依赖默认,显式控制边界
真要保留 FixedThreadPool 的稳定性,又不想埋 OOM 隐患,就得绕过 Executors 工厂方法,手写 ThreadPoolExecutor 构造:
- 用
new LinkedBlockingQueue<Runnable>(1000)替代无界队列,配合AbortPolicy或自定义拒绝逻辑 - 或换用
SynchronousQueue(零容量),逼迫线程池在 corePoolSize 耗尽后立即扩容(但 FixedThreadPool 的 max=core,所以这招对 newFixedThreadPool 无效——正说明它天生不适合弹性) - 更务实的做法:改用
new ThreadPoolExecutor(n, n, 0L, ...)+ 有界队列 + 拒绝策略,把“隐式无界”变成“显式可控”

















