LinkedBlockingQueue vs ConcurrentLinkedQueue

Ada*_*ski 104 java queue concurrency multithreading data-structures

我的问题涉及前面提到的这个问题.在我使用队列进行生产者和消费者线程之间的通信的情况下,人们通常会建议使用LinkedBlockingQueueConcurrentLinkedQueue

使用一个优于另一个的优点/缺点是什么?

从API的角度来看,我可以看到的主要区别是a LinkedBlockingQueue可以选择性地限制.

Jon*_*eet 103

对于生产者/消费者线程,我不确定这ConcurrentLinkedQueue是否是一个合理的选择 - 它没有实现BlockingQueue,这是生产者/消费者队列IMO的基本接口.poll()如果你没有找到任何东西,你必须打电话,稍等一下,然后再次轮询等......导致新项目进入时的延迟,以及空闲时的低效率(由于睡眠不必要地唤醒) .

来自BlockingQueue的文档:

BlockingQueue 实现主要用于生产者 - 消费者队列

我知道并不严格地说只有阻塞队列应该用于生产者 - 消费者队列,但即便如此......

  • 当你需要从很多线程访问队列时,你不需要"等待"它. (27认同)
  • 谢谢乔恩 - 我没有注意到.那你在哪里/为什么要使用ConcurrentLinkedQueue? (4认同)
  • 如果您的线程正在检查多个队列,`ConcurrentLinkedQueue`也很有用。例如,在多租户服务器中。假设出于隔离的原因,您不会使用单个阻塞队列,而是使用租户区分符。 (2认同)

Ale*_*lcu 63

这个问题值得一个更好的答案.

Java ConcurrentLinkedQueue是基于Maged M. Michael和Michael L. Scott着名的非阻塞无锁队列算法.

"非阻塞"作为争用资源(我们的队列)的术语,意味着无论平台的调度程序做什么,如中断线程,或者有问题的线程太慢,其他线程争用相同的资源仍然可以进步.例如,如果涉及锁定,则可以中断持有锁定的线程,并且将阻止等待该锁定的所有线程.synchronizedJava中的内部锁(关键字)也可能会对性能造成严重损失 - 例如,当涉及到偏向锁定并且确实存在争用时,或者在VM决定在自旋宽限期后"膨胀"锁定并阻止竞争线程之后...这就是为什么在许多情况下(低/中等争用的场景),对原子引用进行比较和设置可以更加有效,这正是许多非阻塞数据结构正在做的事情.

Java ConcurrentLinkedQueue不仅是非阻塞的,而且具有生产者不与消费者竞争的强大属性.在单一的生产者/单一消费者场景(SPSC)中,这实际上意味着没有争论可言.在多生产者/单一消费者场景中,消费者不会与生产者竞争.当多个生成器尝试时offer(),此队列确实存在争用,但这是根据定义的并发性.它基本上是一个通用且高效的非阻塞队列.

至于它不是一个BlockingQueue好的,阻塞线程在队列上等待是设计并发系统的一种非常糟糕的方式.别.如果您无法弄清楚如何ConcurrentLinkedQueue在消费者/生产者场景中使用a ,那么只需切换到更高级别的抽象,就像一个好的actor框架.

  • @AlexandruNedelcu你不能做一个像"非常可怕"的广泛声明,你经常使用的演员框架使用线程池,你自己就是*BlockingQueue*.如果您需要一个高度反应的系统,并且您知道如何处理背压(阻塞队列缓解的事情),那么非阻塞显然是优越的.但是..通常阻塞IO和阻塞队列可能会执行非阻塞,特别是如果你有长时间运行的IO绑定任务并且不能被n'征服. (10认同)
  • 根据你的最后一段,你为什么说等待队列是一种设计并发系统的可怕方法?如果我们有一个拥有10个线程的线程组从任务队列中执行任务,那么当任务队列少于10个任务时阻塞有什么问题? (7认同)
  • @AlexandruNedelcu 虽然我总体上同意你关于背压的观点,但我还没有看到从上到下的“无锁”系统。在技​​术栈的某个地方,无论是 Node.js、Erlang、Golang,它都使用某种等待策略,无论是阻塞队列(锁)还是 CAS 旋转其阻塞,在某些情况下,传统的锁策略更快。由于一致性的原因,很难没有锁,这对于阻塞 io 和调度程序(即生产者/消费者)尤其重要。ForkJoinPool 适用于短时间运行的任务,并且它仍然具有 CAS 旋转锁。 (3认同)

dce*_*chi 28

LinkedBlockingQueue当队列为空或满并且相应的消费者/生产者线程进入休眠状态时阻止消费者或生产者.但是这种阻塞功能带来了成本:每个put或take操作都是生产者或消费者之间的争用(如果很多),因此在许多生产者/消费者的情况下,操作可能会更慢.

ConcurrentLinkedQueue在其put/take操作中没有使用锁而是使用CAS可能会减少许多生产者和消费者线程的争用.但是作为一个"等待免费"的数据结构,ConcurrentLinkedQueue在空时不会阻塞,这意味着消费者需要通过"忙等待" 来处理take()返回null值,例如,消费者线程占用CPU.

那么哪个"更好"取决于消费者线程的数量,它们消耗/生产的速率等.每个方案都需要一个基准.

ConcurrentLinkedQueue明显更好的一个特殊用例是当生产者首先生产某些东西并通过将工作放在队列中并且仅在消费者开始消费之后完成他们的工作时,知道他们将在队列为空时完成.(这里不是生产者 - 消费者之间的并发,而只是生产者 - 生产者和消费者 - 消费者之间的并发)