选arrayblockingqueue还是linkedblockingqueue取决于场景:前者适合长度可预估、内存敏感、吞吐稳定;后者适合速率波动大、避免阻塞生产者。根本差异在锁机制与容量语义,而非数据结构本身。

ArrayBlockingQueue 和 LinkedBlockingQueue 哪个该选?
看场景:如果队列长度可预估、内存敏感、要求吞吐稳定,选 ArrayBlockingQueue;如果生产消费速率波动大、不想轻易阻塞生产者、能接受轻微锁竞争开销,选 LinkedBlockingQueue。
两者根本差异不在“数组 vs 链表”,而在于锁机制和容量语义:
-
ArrayBlockingQueue用一把ReentrantLock+ 两个Condition(notFull/notEmpty),所有入队出队串行化,公平性可配,但吞吐上限受单锁限制 -
LinkedBlockingQueue默认用两把锁(takeLock和putLock),入队和出队可并发执行,但节点对象分配带来 GC 压力,且默认容量是Integer.MAX_VALUE—— 表面“无界”,实则可能 OOM - 别被“有界/无界”误导:
ArrayBlockingQueue构造必须指定容量,LinkedBlockingQueue若传了容量才是真有界;不传时,它只是“上限极高”,不是线程安全的无限扩容容器
PriorityBlockingQueue 真的适合做任务调度吗?
适合「按优先级消费」,但不适合「延时调度」或「严格时效控制」——它不支持 Delay 接口,也不保证插入顺序,更不处理时间戳。
常见误用是拿它替代 DelayQueue 做定时任务:
-
PriorityBlockingQueue只按自然序或Comparator排序,元素本身必须实现Comparable或构造时传Comparator - 它不检查元素是否“到期”,
take()永远取堆顶(最高优先级),哪怕这个任务本该 5 分钟后才执行 - 若你往里塞
Runnable+ 优先级字段,没问题;但塞Delayed对象,不会自动等待,take()立刻返回 - 底层是可扩容的二叉堆,插入/删除都是
O(log n),大量动态增删优先级时注意性能抖动
SynchronousQueue 为什么一放就卡住?
因为它根本不存数据 —— put() 必须等到另一个线程正好在调用 take(),反之亦然。这不是 bug,是设计。
典型卡死场景和解法:
- 单线程里先
put()再take():必然死锁,因为没有“另一个线程”来配对 - 生产者比消费者多,且消费者启动慢:所有
put()都在等没人take(),线程全挂起 - 正确用法只有一种:纯手递手交接,比如线程池的
newCachedThreadPool()就用它做任务队列,靠线程动态伸缩来保证“总有线程等着接” - 调试时可用
offer(E, timeout, unit)替代put(),避免无限等待;但超时失败后,任务就得自己兜底重试或丢弃
向 BlockingQueue 放 null 会怎样?
直接抛 NullPointerException,没有任何商量余地。
这不是防御性编程建议,是接口契约强制要求:
-
BlockingQueue规定null是非法值,所有实现类(ArrayBlockingQueue、LinkedBlockingQueue、PriorityBlockingQueue等)在add()、offer()、put()时都会立刻检查 - 原因:内部用
null作哨兵值,比如poll()空队列时返回null,如果允许存null,就无法区分“队列空”和“取到一个 null 元素” - 如果你的数据源天然含
null(比如 Map.get() 结果),必须在外层做非空判断或包装,例如转成Optional<t></t>或自定义空对象
最容易被忽略的一点:连 remove(null) 都会返回 false,不是抛异常,但行为容易让人误以为“删掉了”,其实什么都没发生。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











