为什么在有LinkedBlockingQueue的情况下要使用ConcurrentLinkedQueue?

11
如果我有 LinkedBlockingQueue 的话,为什么要使用 ConcurrentLinkedQueue 呢?我知道 ConcurrentLinkedQueue 是非阻塞的,但是 LinkedBlockingQueue 也可以像 ConcurrentLinkedQueue 一样工作。我会使用 put()/offer() 方法进行插入,使用 poll() 方法进行删除。如果队列为空,poll() 方法不会等待。而且,LinkedBlockingQueue 也是无界的。因此我可以使用它。
到目前为止,我发现两者的区别在于,ConcurrentLinkedQueue 使用硬件级别的同步机制和比较和交换,而 LinkedBlockingQueue 使用 ReentrantLock
1个回答

6
主要区别在于ConcurrentLinkedQueue无需等待的,而不仅仅是无锁的(具体区别?),而LinkedBlockingQueue则天生需要锁定。
你可以通过使用LinkedBlockingQueuepoll来模拟ConcurrentLinkedQueue,但实现仍然需要锁定,从而降低了系统的并发性能。
下面是一个不太精确的微基准测试,检查在不阻塞的情况下通过这两个并发队列传递10,000个对象的速度,并计算循环中对poll()的总调用次数:
AtomicInteger total = new AtomicInteger();
ConcurrentLinkedQueue<Integer> q = new ConcurrentLinkedQueue<>();
Thread thread = new Thread(() -> {
    int remaining = 10000;
    while (remaining != 0) {
        total.incrementAndGet();
        if (q.poll() != null) {
            remaining--;
        }
    }
});
Integer[] first100 = new Integer[100];
for (int i = 0 ; i != 100 ; i++) {
    first100[i] = i;
}
long start = System.nanoTime();
thread.start();
for (int i = 0 ; i != 10000 ; i++) {
    q.add(first100[i%100]);
}
thread.join();
long runtime = System.nanoTime() - start;

这个想法是在没有其他处理的情况下测量队列的“吞吐量”。使用ConcurrentLinkedQueue,在读取线程中进行60K次迭代(演示1),任务完成时间为11.23毫秒;而使用LinkedBlockingQueue,在100K次迭代中任务完成时间为23.46毫秒(演示2)。


请问您能否详细说明一下,“如果您对多个此类阻塞队列不小心处理,您的系统可能会陷入死锁”的含义? - sdindiver
@sdindiver 我无法提供死锁的示例,因此我用一个演示替换了文本,展示了锁定与无锁队列之间的一些时间差异。 - Sergey Kalinichenko
有趣,所以您是在说额外的加锁开销更慢,因此有更多的“失败”轮询,并且这会增加额外的运行时间成本? - gdlamp
@gdlamp 是的,这基本上是我认为正在发生的事情。锁定会增加运行时间,并且“失败”的投票比例也会随之增加。 - Sergey Kalinichenko

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接