为什么不推荐使用 Executors 创建线程池

一、文章概述

Executors 是 JDK 提供的线程池工具类,虽然使用简单,但生产环境严禁使用。其封装的线程池存在无界队列、无界线程、参数不可控三大致命隐患,极易引发 OOM 崩溃和服务雪崩。本文结合具体场景说明隐患原因,并详解 ThreadPoolExecutor 核心参数与执行逻辑。

二、三大隐患及场景举例

1. 无界队列:内存溢出 OOM

newFixedThreadPoolnewSingleThreadExecutor 使用无界 LinkedBlockingQueue(默认容量 Integer.MAX_VALUE),任务会无限堆积。
场景:核心线程数为 1,每个任务执行 1 秒,每秒提交 100 个任务。1 分钟后队列将堆积 5940 个任务,每个任务对象约 1KB,仅队列就占用近 6MB;若持续提交,最终会耗尽堆内存触发 OOM。

2. 无界线程:CPU 耗尽 + OOM

newCachedThreadPool 的最大线程数为 Integer.MAX_VALUE,只要有任务就创建新线程。
场景:短时间内提交 1 万个任务,会立即创建 1 万个线程。每个线程默认栈内存 1MB,仅栈空间就占用 10GB;同时大量线程切换会导致 CPU 使用率飙升至 100%,服务完全卡死。

3. 参数不可控:不符合生产规范

Executors 封装的方法无法自定义核心参数:

  • 无法设置有界队列容量,只能使用无界队列
  • 无法自定义拒绝策略,默认直接抛异常
  • 无法设置线程名称,排查问题时无法定位线程归属
  • 无法调整非核心线程的空闲回收时间

三、ThreadPoolExecutor 核心参数与流程

参数 核心作用
corePoolSize 常驻核心线程数
maximumPoolSize 最大线程数
keepAliveTime 非核心线程空闲超时时间
workQueue 任务阻塞队列
handler 拒绝策略

提交任务

核心线程满?

核心线程执行

队列满?

入队等待

达到最大线程?

创建非核心线程

执行拒绝策略

四、核心线程 vs 非核心线程

类型 特点
核心线程 常驻线程池,不会被回收
非核心线程 临时创建,空闲超时自动回收

五、四种拒绝策略

  1. AbortPolicy:直接抛异常(默认)
  2. CallerRunsPolicy:调用者线程执行任务
  3. DiscardPolicy:直接丢弃当前任务
  4. DiscardOldestPolicy:丢弃队列最旧任务

总结

Executors 创建的线程池存在严重生产风险,必须手动使用 ThreadPoolExecutor 构建。根据业务场景合理设置核心线程数、有界队列容量和拒绝策略,才能保证服务稳定运行。

更多推荐