选有界队列是防止内存溢出的底线做法,必须显式指定容量(如arrayblockingqueue(256)或linkedblockingqueue(256)),搭配callerrunspolicy拒绝策略和命名threadfactory,禁用executors工厂方法。

Java线程池中,**选有界队列是防止内存溢出的底线做法**,无界队列在绝大多数业务场景下都不应作为默认选择——它表面“不拒绝任务”,实则把OOM从运行时延后到堆内存被填满那一刻。
优先用有界队列,明确容量上限
无界队列(如 new LinkedBlockingQueue())底层容量是 Integer.MAX_VALUE,相当于放任任务无限堆积。一个含 JSON 请求体、数据库参数、上下文对象的 Runnable,往往占用几 MB 堆内存;几百个就可能触发 Full GC,上千个极易 OOM。
- 改用
ArrayBlockingQueue或带容量的LinkedBlockingQueue(256),强制设一个可评估、可监控的数字 - 容量不是越大越好:设成 10000 看似“保险”,实则是把崩溃时间从第 2 分钟推迟到第 20 分钟,问题本质没变
- 队列容量需与线程能力匹配:比如核心线程数为 8,队列设 256,意味着最多缓冲 256 个待处理任务;超出即触发拒绝策略,而非静默吃内存
按业务特征选具体队列类型
不同队列行为差异大,不能只看“有没有界”:
- ArrayBlockingQueue:数组实现、固定大小、线程安全,适合资源敏感型服务(如支付、风控),内存占用可控,但队列满时拒绝更频繁
- LinkedBlockingQueue(指定容量):链表实现,吞吐略高,适合任务体小、提交节奏稳的后台作业(如日志归档、定时报表)
- SynchronousQueue:不存储任务,直接移交线程执行,适合高并发短任务(如 API 网关转发),但要求线程池能快速响应,否则任务直接被拒
配套必须做两件事:拒绝策略 + 命名线程工厂
有界队列只是第一步,没有拒绝策略和可观测性,等于装了刹车却没配刹车灯:
- 拒绝策略推荐
CallerRunsPolicy:队列满时由提交线程自己执行任务,天然反压,能自动减缓上游提交速度,避免雪崩 - 务必传入命名的
ThreadFactory,例如用 Guava 的ThreadFactoryBuilder生成"order-process-%d"这类名称,方便从堆栈、监控、日志里快速定位是哪个线程池卡住了 - 避免默认
AbortPolicy(直接抛RejectedExecutionException),除非业务明确允许丢任务;关键路径建议自定义策略,记录日志 + 触发降级逻辑(如写入死信队列)
别踩这些典型配置坑
很多 OOM 案例都源于“看起来没问题”的默认构造:
-
Executors.newFixedThreadPool(n)→ 底层是LinkedBlockingQueue(Integer.MAX_VALUE),本质无界 -
Executors.newCachedThreadPool()→ 最大线程数Integer.MAX_VALUE,高并发下线程数疯涨,栈内存耗尽 + CPU 打满 -
Executors.newSingleThreadExecutor()→ 同样无界队列,且封装为不可调参的FinalizableDelegatedExecutorService,异常后线程静默死亡,任务永久积压
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











