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 操作可以并发进行
// 吞吐量更高