并发包中的ConcurrentLinkedQueue和LinkedBlockingQueue有什么区别? - 《Java 核心技术》笔记

  • Concurrent* 容器,基于lock-free,在常见的多线程访问场景,一般可以提供较高吞吐量;
  • LinkedBlockingQueue内部基于锁,并提供了BlockingQueue的等待性方法。

Concurrent* 性质

java.util.concurrent包提供的容器(Queue、List、Set)、Map从命名上可以大概分为Concurrent* 、CopyOnWrite和Blocking等三类:

  • Concurrent没有类似CopyOnWrite之类容器相对较重的修改开销;
  • Concurrent提供了较低的遍历一致性。比如当利用迭代器遍历时,如果容器发生改变,迭代器仍然可以继续进行遍历;
  • 与弱一致性对应的,是同步容器常见的“fail-fast”,检测到容器在遍历中发生了修改,则抛出ConcurrentModificationException,不再遍历;
  • size等操作准确性有限;
  • 读取的性能具有一定的不确定性。

线程安全队列(Queue)

791750d6fe7ef88ecb3897e1d029f079

绝大部分Queue都是实现了BlockingQueue接口。Blocking意味着提供了特定的等待性操作,take时等待元素进队,put是等待队列出现空位。

是否有界

  • ArrayBlockingQueue是典型的有界队列,内部以final的数组保存数据,创建时就制定了容量;
  • LinkedBlockingQueue,其内部行为和代码都是基于有界的逻辑实现的,但是如果在创建时没有指定容量,则会被自动设置为Integer.MAX_VALUE。成为无界队列;
  • SynchronousQueue,每个删除操作都要等待插入操作,每个插入也都要等待删除操作。这个队列容量是0;
  • PriorityBlockingQueue,无边界;
  • DelayedQueue、LinkedTransferQueue,无边界。

实现——BlockingQueue一般都基于锁

LinkedBlockingQueue的实现:

/** Lock held by take, poll, etc */
private final ReentrantLock takeLock = new ReentrantLock();

/** Wait queue for waiting takes */
private final Condition notEmpty = takeLock.newCondition();

/** Lock held by put, offer, etc */
private final ReentrantLock putLock = new ReentrantLock();

/** Wait queue for waiting puts */
private final Condition notFull = putLock.newCondition();

与ArrayBlockingQueue不同,后者的notEmpty、notFull都是同一个再入锁的条件变量。而LinkedBlockingQueue改进了锁操作的粒度,头、尾操作使用不同的锁。

LinkedBlockingQueue的take操作:

public E take() throws InterruptedException {
    final E x;
    final int c;
    final AtomicInteger count = this.count;
    final ReentrantLock takeLock = this.takeLock;
    takeLock.lockInterruptibly();
    try {
        while (count.get() == 0) {
            notEmpty.await();
        }
        x = dequeue();
        c = count.getAndDecrement();
        if (c > 1)
            notEmpty.signal();
    } finally {
        takeLock.unlock();
    }
    if (c == capacity)
        signalNotFull();
    return x;
}

代码中的选择

  • 边界:ArrayBlockingQueue有明确的容量限制,而LinkedBlockingQueue取决于创建的时候是否指定;
  • 空间利用:ArrayBlockingQueue比LinkedBlockingQueue紧凑,因为不需要创建节点,但是初始分配阶段需要一段连续内存,所以初始内存需求更大;
  • 通用场景中,因为LinkedBlockingQueue头尾使用更细粒度的锁操作,所以吞吐量比ArrayBlockingQueue高;
  • ArrayBlockingQueue性能比较好预测。
comments powered by Disqus