#BlockingQueue 实现类对比
面试官问:"Java 有哪些阻塞队列实现?"
候选人小宋答:"有 ArrayBlockingQueue 和 LinkedBlockingQueue。"
面试官追问:"还有什么?有什么区别?"
小宋答不上来。
【面试官心理】 阻塞队列是 Java 并发编程的核心组件。能说出各实现的区别和适用场景的候选人,说明有生产经验。
#一、阻塞队列家族 🔴
| 实现类 | 底层结构 | 是否有界 | 特点 |
|---|---|---|---|
| ArrayBlockingQueue | 数组 | 有界 | 公平锁,性能一般 |
| LinkedBlockingQueue | 链表 | 可选有界/无界 | 锁分离,性能好 |
| LinkedBlockingDeque | 双向链表 | 有界 | 双端操作 |
| PriorityBlockingQueue | 堆 | 无界 | 优先级排序 |
| DelayQueue | 优先级队列 | 无界 | 延迟获取 |
| SynchronousQueue | 无存储 | 零容量 | 同步交接 |
| LinkedTransferQueue | 链表 | 无界 | TransferQueue,支持直接 transfer |
#二、ArrayBlockingQueue vs LinkedBlockingQueue 🔴
// ArrayBlockingQueue
// - 底层:Object[] 数组
// - 有界:构造函数必须指定容量
// - 一把锁:put 和 take 共用同一个锁
// - 公平/非公平:可选择
ArrayBlockingQueue<String> queue1 = new ArrayBlockingQueue<>(100);
ArrayBlockingQueue<String> queue2 = new ArrayBlockingQueue<>(100, true); // 公平
// LinkedBlockingQueue
// - 底层:单向链表
// - 可选有界/无界(默认 Integer.MAX_VALUE)
// - 两把锁:putLock + takeLock(锁分离)
// - 吞吐量更高
LinkedBlockingQueue<String> queue3 = new LinkedBlockingQueue<>(100);
LinkedBlockingQueue<String> queue4 = new LinkedBlockingQueue<>(); // 无界#锁分离优化
// LinkedBlockingQueue 内部结构
public class LinkedBlockingQueue<E> {
private final ReentrantLock putLock = new ReentrantLock();
private final ReentrantLock takeLock = new ReentrantLock();
// put 操作:持 putLock
public void put(E e) throws InterruptedException {
if (e == null) throw new NullPointerException();
int c = -1;
Node<E> node = new Node<>(e);
putLock.lockInterruptibly();
try {
while (count.get() == capacity) {
notFull.await(); // 队列满,等待
}
enqueue(node);
c = count.getAndIncrement();
if (c + 1 < capacity)
notFull.signal(); // 唤醒等待的 put 线程
} finally {
putLock.unlock();
}
}
// take 操作:持 takeLock
public E take() throws InterruptedException {
E x;
int c = -1;
takeLock.lockInterruptibly();
try {
while (count.get() == 0) {
notEmpty.await(); // 队列空,等待
}
x = dequeue();
c = count.getAndDecrement();
if (c > 1)
notEmpty.signal(); // 唤醒等待的 take 线程
} finally {
takeLock.unlock();
}
}
}#三、SynchronousQueue 🟡
// 同步队列:没有存储空间
// put 操作必须等待 take 操作,反之亦然
// 用于两个线程之间的直接数据交接
SynchronousQueue<String> queue = new SynchronousQueue<>();
// 线程 A:放数据
Thread producer = new Thread(() -> {
try {
queue.put("data"); // 阻塞,直到有线程取走
System.out.println("put 成功");
} catch (InterruptedException e) { }
});
// 线程 B:取数据
Thread consumer = new Thread(() -> {
try {
String data = queue.take(); // 阻塞,直到有线程放入
System.out.println("got: " + data);
} catch (InterruptedException e) { }
});
producer.start();
consumer.start();
// 输出:
// got: data
// put 成功#四、选型指南 🟡
// 需要固定容量、高吞吐量:ArrayBlockingQueue(内存效率高)
// 需要高吞吐量、可选容量:LinkedBlockingQueue(锁分离)
// 需要直接交接、零延迟:SynchronousQueue
// 需要优先级排序:PriorityBlockingQueue
// 需要延迟处理:DelayQueue(如缓存过期、定时任务)
// 需要无界队列(慎用):LinkedBlockingQueue(无参构造)或 ArrayDeque(非阻塞)#五、生产避坑 🟡
// ❌ 危险:LinkedBlockingQueue 无参构造(Integer.MAX_VALUE)
// 如果生产者速度远大于消费者,可能 OOM
LinkedBlockingQueue<String> queue = new LinkedBlockingQueue<>();
// 相当于无界队列!
// ✅ 正确:设置合理容量
LinkedBlockingQueue<String> queue = new LinkedBlockingQueue<>(10000);
// 或使用有界队列 + 拒绝策略#六、追问升级
面试官:"ArrayBlockingQueue 为什么吞吐量比 LinkedBlockingQueue 低?"
// ArrayBlockingQueue:单锁
// put 和 take 共用同一个 ReentrantLock
// 竞争激烈时,put 和 take 会互相阻塞
// LinkedBlockingQueue:锁分离
// putLock 和 takeLock 是两把独立的锁
// put 和 take 操作可以并发进行
// 吞吐量更高