跳转至

LinkedBlockingQueue:线程安全的有界队列

一、是什么

基于链表的阻塞队列,默认容量 Integer.MAX_VALUE(建议指定)。

BlockingQueue<String> q = new LinkedBlockingQueue<>(100);
q.put("a");    // 满了阻塞
q.take();      // 空了阻塞

二、结构

head ↔ node ↔ node ↔ tail
  • 两个锁:takeLock(读)、putLock(写)。
  • 两个条件:notEmpty(读等待)、notFull(写等待)。

三、源码结构

public class LinkedBlockingQueue<E> {
    static class Node<E> { E item; Node<E> next; }

    private final int capacity;
    private final AtomicInteger count = new AtomicInteger();

    private final ReentrantLock takeLock = new ReentrantLock();
    private final Condition notEmpty = takeLock.newCondition();

    private final ReentrantLock putLock = new ReentrantLock();
    private final Condition notFull = putLock.newCondition();
}

四、为什么用两个锁

ArrayBlockingQueue 用一个锁,读写互斥。LinkedBlockingQueue 用两个锁,读和写可以并行,吞吐更高。

put: 拿 putLock → 写 tail → 唤醒 notEmpty
take: 拿 takeLock → 读 head → 唤醒 notFull

五、put 流程

public void put(E e) throws InterruptedException {
    int c = -1;
    Node<E> node = new Node<>(e);
    final ReentrantLock putLock = this.putLock;
    final AtomicInteger count = this.count;
    putLock.lockInterruptibly();
    try {
        while (count.get() == capacity) {
            notFull.await();       // 满了,等
        }
        enqueue(node);
        c = count.getAndIncrement();
        if (c + 1 < capacity)
            notFull.signal();      // 还有空位,唤醒其他写
    } finally {
        putLock.unlock();
    }
    if (c == 0)
        signalNotEmpty();          // 有元素了,唤醒读
}

六、take 流程

对称:拿 takeLock → 空了等 notEmpty → 取 head → count 减 → 唤醒 notFull。

七、对比 ArrayBlockingQueue

LinkedBlockingQueue ArrayBlockingQueue
结构 链表 数组
读写双锁 单锁
吞吐 高(读写并行) 低(读写互斥)
容量 默认无界 必须指定
GC 节点对象 数组连续

线程池用哪个

Executors.newFixedThreadPool 用 LinkedBlockingQueue(无界)。 Executors.newCachedThreadPool 用 SynchronousQueue(不存元素)。