无界队列使maximumPoolSize失效,因其容量设为Integer.MAX_VALUE,队列几乎永不“满”,故线程池仅维持corePoolSize线程;有界队列需按QPS与耗时合理设置容量,否则仍会触发拒绝;SynchronousQueue可主动限流,使maximumPoolSize成为真实并发上限。
无界队列不是“没限制”,而是用 integer.max_value(约 21 亿)假装有边界——它在逻辑上等同于无限,实际运行中根本不会“满”。只要队列没满,线程池就永远不会创建超出 corepoolsize 的线程,maximumpoolsize 形同虚设。
为什么无界队列让 maximumPoolSize 失效
线程池扩容只发生在两个条件同时满足时:队列已满 且 当前线程数 。而 LinkedBlockingQueue 默认构造(new LinkedBlockingQueue())的容量是 Integer.MAX_VALUE,意味着:
- 即使每秒涌入 1 万任务,跑满一小时也才积压 3600 万,离“满”还差 99.98%;
- JVM 堆内存早被撑爆、GC 频繁或服务 OOM 挂掉,队列也远未触达 capacity;
- 所以扩容分支永远不执行,活跃线程数卡死在 corePoolSize,所有新任务只能排队。
一眼识别你是否踩了无界陷阱
别只看代码写了 LinkedBlockingQueue,重点查三处:
-
构造方式:是否显式传入容量?如
new LinkedBlockingQueue(200)是有界;new LinkedBlockingQueue()或new LinkedBlockingQueue(Integer.MAX_VALUE)就是无界 -
运行时验证:用 Arthas 执行
ognl '@your.Executor@queue.remainingCapacity()',若返回值极大(如 >10⁹),基本可判定为无界 -
监控反推:观察
executor.getQueue().size()持续上涨但executor.getActiveCount()始终 ≈corePoolSize,说明任务全堵在队列里,没人消费,更不会扩容
有界 ≠ 安全,关键看容量是否匹配业务水位
设成 ArrayBlockingQueue(100) 并不自动解决问题。如果平均任务耗时 200ms、峰值 QPS 达到 500,那每秒就有 100 个任务进队,1 秒就满;而核心线程若只有 4 个,处理能力仅约 20 QPS(4 × 1000/200),队列迅速堆满后才会扩线程——这时你得确认:maximumPoolSize 是否真能扛住后续涌来的压力?
- 若 maximumPoolSize=20,理论吞吐上限 ≈ 20 × 5 = 100 QPS(按 200ms/任务算),仍远低于 500 QPS,拒绝必然发生
- 若盲目拉高到 200,又可能引发上下文切换风暴和内存溢出
- 真正有效的做法是:先压测得出单线程稳定吞吐,再结合 QPS 和耗时反推合理 maximumPoolSize,同时把队列容量设为「可接受的最大排队延迟 × QPS」,例如允许最多排队 2 秒,则队列大小 ≈ 500 × 2 = 1000
替代思路:用 SynchronousQueue 主动拒绝,倒逼上游限流
当业务对延迟敏感、不允许排队时,直接弃用缓冲队列:
- 选用
SynchronousQueue:它不存储任务,每次submit都必须立刻有空闲线程承接,否则立即触发拒绝策略 - 配合
AbortPolicy或自定义拒绝逻辑,快速返回 429 Too Many Requests,把背压信号实时透传给网关或客户端 - 这样 maximumPoolSize 就真正成为“并发硬上限”,系统不再隐性积压,故障可预期、可感知、可拦截

















