linkedblockingqueue默认容量为integer.max_value(约21亿),表面无界实则有界,易致oom;双锁仅分离入队出队竞争,同向操作仍争锁;应依业务设合理上界而非依赖默认值。

LinkedBlockingQueue 默认是有界的,但上限大到像无界
它不是真正无界队列(比如 ConcurrentLinkedQueue),而是用 Integer.MAX_VALUE 作为默认容量。表面看“随便塞”,实际一旦队列长度真达到这个值,后续 offer() 或 put() 就会失败或阻塞——但你几乎不可能等到那天,因为堆早爆了。
常见错误现象:OutOfMemoryError: Java heap space 出现在持续生产却消费慢的场景,你以为是“无界”撑得住,其实是 Integer.MAX_VALUE 这个数字在骗你——内存先扛不住。
- 构造时不传参 → 容量 =
Integer.MAX_VALUE(约 21 亿) - 传入
0或负数 → 抛IllegalArgumentException - 传入正整数
n→ 真正有界,满时offer()返回false,put()阻塞
两把锁(takeLock / putLock)不等于读写完全不冲突
它确实用 takeLock 和 putLock 分离了出队和入队操作,但这只解决“同时入+出”的竞争;同方向操作仍需各自锁保护。比如两个线程同时 put(),仍要争 putLock。
性能影响明显的地方:当生产者极多、消费者极少时,putLock 成为瓶颈,而 takeLock 几乎空闲——别被“双锁”宣传带偏,它不是万能并发加速器。
立即学习“Java免费学习笔记(深入)”;
-
size()方法内部会短暂同时持有两把锁,高并发下调用频繁会拖慢整体吞吐 -
remainingCapacity()同样需要双锁,且返回值可能在返回瞬间就失效 - 迭代器
iterator()是弱一致的,不抛ConcurrentModificationException,但也看不到实时增删
和 ArrayBlockingQueue 比,锁粒度优势只在特定场景生效
很多人选 LinkedBlockingQueue 就冲着“双锁”,但真实收益要看操作比例。如果应用里 put() 和 take() 基本是 1:1,那双锁带来的并行提升有限;反倒是 ArrayBlockingQueue 的单锁 + 数组连续内存,在 CPU 缓存友好性上更稳。
容易踩的坑:用 LinkedBlockingQueue 替换 ArrayBlockingQueue 以为能“自动扩容+更高并发”,结果发现 GC 压力飙升(链表节点对象多)、缓存行失效更频繁、监控里 putLock 竞争时间反而变长。
- 链表节点(
Node)每次put()都 new 对象 → 堆压力 & GC 频率上升 - 数组元素复用(
ArrayBlockingQueue)→ 更少对象分配,更优局部性 - 若队列长度长期稳定在几百以内,
ArrayBlockingQueue往往更快
Integer.MAX_VALUE 这个默认值,本质是妥协不是设计
源码里写死的 Integer.MAX_VALUE 不是为了让你“当无界用”,而是为了避免构造时强制用户填容量——毕竟很多场景下开发者根本说不清合理上限是多少。但它把决策延迟到了运行时,代价是内存失控风险后移。
真正该做的,是结合业务吞吐与响应要求,主动设一个**有物理意义的上界**:比如下游消费能力峰值是 1000 QPS,处理延迟 ≤ 5s,那队列容量设成 5000 比设成 Integer.MAX_VALUE 负责得多。
- 设太小 → 频繁拒绝/丢消息,但至少你知道边界在哪
- 设太大(尤其默认)→ OOM 前毫无预警,堆 dump 里全是
Node实例 - 监控重点不是“是否满”,而是 “
remainingCapacity()是否持续低于阈值”
最麻烦的不是容量本身,是默认值掩盖了容量决策这件事——它让很多人忘了队列从来就不是“放着就行”的组件。










