高 进阶
BlockingQueue实现原理#
一句话答案#
BlockingQueue 通过 ReentrantLock + Condition(notEmpty/notFull)实现线程安全的阻塞入队出队:队满时 put 阻塞等待 notFull,队空时 take 阻塞等待 notEmpty。
核心要点
核心操作#
| 操作 | 抛异常 | 返回特殊值 | 阻塞 | 超时 |
|---|---|---|---|---|
| 入队 | add(e) | offer(e) | put(e) | offer(e, timeout) |
| 出队 | remove() | poll() | take() | poll(timeout) |
ArrayBlockingQueue 原理#
final ReentrantLock lock; // 一把锁
final Condition notEmpty; // 队列非空条件
final Condition notFull; // 队列非满条件
// put: 队满时阻塞
public void put(E e) {
lock.lockInterruptibly();
try {
while (count == items.length)
notFull.await(); // 队满,等 notFull
enqueue(e); // 入队,调 notEmpty.signal()
} finally { lock.unlock(); }
}
// take: 队空时阻塞
public E take() {
lock.lockInterruptibly();
try {
while (count == 0)
notEmpty.await(); // 队空,等 notEmpty
return dequeue(); // 出队,调 notFull.signal()
} finally { lock.unlock(); }
}java用 while 不用 if——防止虚假唤醒。
Array vs Linked#
| 维度 | ArrayBlockingQueue | LinkedBlockingQueue |
|---|---|---|
| 锁 | 一把锁(读写互斥) | 两把锁(putLock + takeLock) |
| 并发 | 较低 | 较高(读写可并行) |
| 内存 | 预分配,无 GC | 每次 put 创建 Node |
| 默认容量 | 必须指定 | Integer.MAX_VALUE(危险!) |
在线程池中的应用#
| 队列 | 特点 | 适用 |
|---|---|---|
| ArrayBlockingQueue | 有界 | 严格限制队列大小 |
| LinkedBlockingQueue | 可选有界 | 通用(必须设容量) |
| SynchronousQueue | 无容量,直接传递 | 高吞吐 |
| PriorityBlockingQueue | 按优先级 | 优先级调度 |
陷阱: LinkedBlockingQueue 不设容量 → 默认 MAX_VALUE → OOM。Executors.newFixedThreadPool 就用了无界队列。
面试回答(2分钟版)
BlockingQueue 是 Java 并发中实现生产者-消费者模式的核心组件,底层通过 ReentrantLock 加两个 Condition 来保证线程安全的阻塞入队出队。具体来说,队列满时 put 操作会阻塞在 notFull 条件上等待,队列空时 take 操作会阻塞在 notEmpty 条件上等待,入队成功后唤醒 notEmpty,出队成功后唤醒 notFull,形成完整的协作机制。等待条件用 while 而不是 if 来防止虚假唤醒。常用实现中,ArrayBlockingQueue 用一把锁读写互斥,LinkedBlockingQueue 用两把锁(putLock 和 takeLock)使读写可并行,并发性能更高。实际使用中有一个非常重要的陷阱:LinkedBlockingQueue 默认容量是 Integer.MAX_VALUE,相当于无界队列,生产中必须显式设置容量,否则队列无限增长会导致 OOM。
追问与易错
追问方向:
- “为什么用 while 不用 if?”→ 防止虚假唤醒
- “LinkedBlockingQueue 为什么用两把锁?”→ 入队操作尾部、出队操作头部,不冲突
易错点:
- ❌ “BlockingQueue 是无锁的”——基于 ReentrantLock + Condition
- ❌ “ArrayBlockingQueue 读写可并行”——只有一把锁,读写互斥