executors.newfixedthreadpool易oom的根本原因是其默认使用无界linkedblockingqueue,任务持续提交而消费慢时队列无限堆积导致堆内存耗尽;newcachedthreadpool因synchronousqueue+无限线程数更易触发unable to create new native thread;newsinglethreadexecutor和newscheduledthreadpool同理存在无界队列隐患。

Executors.newFixedThreadPool 为什么容易 OOM
根本原因是它内部使用的 LinkedBlockingQueue 默认无界(容量为 Integer.MAX_VALUE),任务持续提交但消费速度跟不上时,队列无限堆积,最终耗尽堆内存。这不是线程池“不安全”,而是设计上把背压策略完全交给了调用方,而多数人没意识到这点。
- 典型场景:上游 HTTP 请求洪峰 + 下游 DB 响应变慢 → 任务在队列里越积越多
-
newCachedThreadPool更危险:核心线程数 0、最大线程数Integer.MAX_VALUE,加上SynchronousQueue,会疯狂创建线程,直接触发OutOfMemoryError: unable to create new native thread -
newSingleThreadExecutor和newScheduledThreadPool同样使用无界队列,存在相同隐患
自定义线程池必须显式控制的三个参数
绕过 Executors 工厂方法后,你得亲手填满 ThreadPoolExecutor 构造函数的七个参数,但真正关键的是前五个,尤其以下三个必须按业务收敛:
-
corePoolSize:常驻线程数,别拍脑袋设成 CPU 核数 × 2;IO 密集型可稍高(如 20~50),CPU 密集型建议 ≤ CPU 核数 + 1 -
maximumPoolSize:最大线程数,要和拒绝策略联动;设太大等于放弃控制,设太小又浪费资源 -
workQueue:必须用有界队列,比如new ArrayBlockingQueue(100);容量不是越大越好,得结合平均处理时长和可接受排队时长反推(例如:TP99=200ms,允许最长排队 2s → 队列 ≈ 10)
拒绝策略选哪个?要看你的服务是否可降级
默认的 AbortPolicy 直接抛 RejectedExecutionException,对 RPC 接口可能合适(让上游重试),但对后台异步日志就太粗暴了。常见选择:
-
CallerRunsPolicy:由提交线程自己执行任务,能自然减速,适合非关键路径(如统计埋点) - 自定义策略:比如把任务写入 Kafka 或 Redis 延迟队列,后续补偿处理;注意别在拒绝逻辑里再调用
execute(),否则可能栈溢出 - 千万别用
DiscardPolicy默默丢弃——除非你确认丢的是心跳或 ping 类无副作用任务
还有两个容易被跳过的初始化细节
很多人写了 ThreadPoolExecutor 构造,却漏掉线程工厂和拒绝策略的显式指定,导致排查困难:
立即学习“Java免费学习笔记(深入)”;
- 线程名不设
ThreadFactory→ 所有线程叫pool-1-thread-1,线上出问题根本分不清是哪个业务线程池在狂打 GC - 不传
RejectedExecutionHandler→ 默认用AbortPolicy,但异常堆栈里看不到是哪个线程池抛的,得靠线程名或监控指标反推 - 示例:
new ThreadPoolExecutor(4, 8, 60L, TimeUnit.SECONDS, new ArrayBlockingQueue(100), r -> new Thread(r, "order-process-pool-%d"), new ThreadPoolExecutor.CallerRunsPolicy())










