LinkedBlockingQueue与ConcurrentLinkedQueue


112

我的问题涉及到这个问题,刚才问。在使用队列在生产者线程与使用者线程之间进行通信的情况下,人们通常会建议使用LinkedBlockingQueueConcurrentLinkedQueue吗?

与另一种相比,使用一种有什么优点/缺点?

从API角度看,我的主要区别是a LinkedBlockingQueue可以有选择地限制。

Answers:


110

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

从BlockingQueue的文档中:

BlockingQueue 实现被设计为主要用于生产者-消费者队列

我知道这并不是严格说生产者-消费者队列只应使用阻塞队列,但即使如此……


4
谢谢乔恩-我没注意到。那么,为什么在何处使用ConcurrentLinkedQueue?
亚当斯基

27
当您需要从很多线程访问队列,但是不需要“等待”它时。
乔恩·斯基特

2
ConcurrentLinkedQueue如果您的线程正在检查多个队列,则A 也很有用。例如,在多租户服务器中。出于隔离的原因,假设您不使用单个阻塞队列,而是使用租户区分符。
LateralFractal '16

仅当我们使用队列中的有队列take()put()仅消耗比额外的资源(同步项)时,您的情况才成立ConcurrentLinkedQueue 。尽管在生产者-消费者方案中
amarnath harish

@Adamski IMO,ConcurrentLinkedQueue只是在多线程环境中使用的链表。最好的类比是ConcurrentHashMap和HashMap。
Nishit

69

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

Java ConcurrentLinkedQueue基于Maged M. Michael和Michael L. Scott的著名算法,用于无阻塞无锁队列。

“无阻塞”在这里是争用资源(我们的队列)的术语,意味着无论平台的调度程序做什么,例如中断线程,或者所讨论的线程太慢,其他线程都在争用同一资源仍然可以进步。例如,如果涉及一个锁,则持有该锁的线程可能会中断,并且所有等待该锁的线程都将被阻塞。synchronizedJava中的内在锁(关键字)也可能带来严重的性能损失-例如偏向锁定时涉及到您并且确实存在争用,或者在VM决定在旋转宽限期之后“膨胀”锁并阻塞竞争线程之后……这就是为什么在许多情况下(低/中争用的情况)进行比较和原子引用上的-sets可以更加高效,而这正是许多非阻塞数据结构正在做的事情。

Java ConcurrentLinkedQueue不仅具有非阻塞性,而且具有生产者无法与消费者抗衡的强大特性。在单一生产者/单一消费者场景(SPSC)中,这实际上意味着没有争执。在多生产者/单一消费者的情况下,消费者将不与生产者竞争。当多个生产者尝试时offer(),此队列确实有争用,但是根据定义,这是并发的。基本上,这是一个通用且高效的非阻塞队列。

至于不是BlockingQueue,阻止线程等待队列是设计并发系统的一种非常糟糕的方式。别。如果您不知道如何ConcurrentLinkedQueue在消费者/生产者场景中使用,则只需切换到更高级别的抽象,例如好的actor框架。


8
在最后一段中,为什么要说在队列上等待是设计并发系统的糟糕方法?如果我们有一个线程组,其中有10个线程从一个任务队列中吃掉任务,那么当任务队列中的任务少于10个时,阻塞又有什么问题呢?
Pacerier,2014年

11
@AlexandruNedelcu您不能做出像“异常可怕”这样的笼统语句,在该语句中,您说要使用的actor框架经常会使用本身就是BlockingQueue的线程池。如果您需要一个高度反应性的系统,并且知道如何处理背压(阻塞队列可以缓解这种情况)比非阻塞显然更好。但是..通常,阻塞IO和阻塞队列通常可以执行非阻塞操作,尤其是当您具有受IO约束且无法被n'分治的长期运行任务时。
亚当·根特

1
@AdamGent-actor框架确实具有基于阻塞队列的邮箱实现,但是在我看来,这是一个错误,因为阻塞无法在异步边界上工作,因此只能在演示中使用。对我而言,这一直是令人沮丧的根源,例如,Akka处理溢出的概念是阻止而不是丢弃消息,直到2.4版本尚未发布。话虽如此,我不认为在某些情况下阻塞队列可能会更好。您还混淆了不应该混淆的两件事。我还没有谈到阻止I / O。
亚历山德鲁·内德尔库

1
@AlexandruNedelcu虽然我在背压方面与您大致相同,但我还没有看到从上到下的“无锁”系统。无论是Node.js,Erlang,Golang还是技术堆栈中的某个位置,是否使用某种等待策略(无论是阻塞队列(锁)还是CAS旋转其阻塞),并且在某些情况下,传统的锁策略都更快。由于一致性,很难没有锁,这对于阻塞io和Producer / Consumer的调度程序尤其重要。ForkJoinPool可用于短期任务,并且仍然具有CAS自旋锁。
亚当·根特

1
@AlexandruNedelcu我想您是否可以向我展示如何为生产者/消费者模式使用ConcurrentLinkedQueue(由于我的弱背压参数而不受限制),这是调度程序和线程池所需的模式,我想我会接受并承认永远不要使用BlockingQueue(并且您不能作弊并委托给其他进行调度的任务,例如akka,因为这反过来将进行阻塞/等待,因为它是生产者/消费者)。
亚当·根特2015年

33

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

ConcurrentLinkedQueue在其put / take操作上未使用锁,而是使用CAS,有可能减少与许多生产者和使用者线程的争用。但是,作为“无等待”数据结构,ConcurrentLinkedQueue在空时不会阻塞,这意味着使用者将需要通过“繁忙等待” 来处理take()返回null值,例如,使用者线程耗尽了CPU。

因此,哪个“更好”取决于使用者线程的数量,使用者消耗/生产的速率等。每种情况都需要一个基准。

一个ConcurrentLinkedQueue明显更好的特定用例是,生产者首先生产产品并通过将工作放入队列中来完成工作,并且只有在消费者开始消费之后才知道,当队列为空时他们将完成工作。(这里生产者-消费者之间没有并发性,只有生产者-生产者和消费者-消费者之间并发)


这里有一个疑问。正如您提到的,当队列为空时,消费者等待。.它等待多长时间。谁会通知它不要等待?
Brinal

@brindal我知道,等待它的唯一方法是循环。这是一个重要的问题,在这里的答案中并未给予足够的重视。仅运行循环等待数据会占用大量处理器时间。当您的粉丝开始歌颂时,您会知道的。唯一的补救方法是使睡眠陷入循环。因此,这在数据流不一致的系统中是一个问题。也许我误解了AlexandruNedelcu的答案,但是一个操作系统本身就是一个并发系统,如果它充满了非阻塞事件循环,那将是非常低效的。
orodbhen

还好,但如果unbounded blockingqueue使用的话会好于CAS基于并行ConcurrentLinkedQueue
amarnath哈里什

@orodbhen睡觉也不能消除浪费。操作系统必须做很多工作才能使线程退出睡眠状态并安排和运行它。如果消息尚不可用,则您的OS所做的工作将浪费掉。我建议最好使用BlockingQueue,因为它是专门为生产者-消费者问题设计的。
Nishit

实际上,我对“消费/生产率”部分非常感兴趣,因此,如果生产率提高,哪个更好?
workplaylifecycle


0

如果队列不可扩展,并且仅包含一个生产者/消费者线程。您可以使用无锁队列(不需要锁定数据访问权限)。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.