【问题标题】:LinkedBlockingQueue vs ConcurrentLinkedQueueLinkedBlockingQueue 与 ConcurrentLinkedQueue
【发布时间】:2010-11-28 10:40:53
【问题描述】:

我的问题与之前提出的this question 有关。在我使用队列在生产者和消费者线程之间进行通信的情况下,人们通常会建议使用LinkedBlockingQueueConcurrentLinkedQueue

使用其中一种的优点/缺点是什么?

从 API 的角度来看,我可以看到的主要区别是 LinkedBlockingQueue 可以选择有界。

【问题讨论】:

    标签: java multithreading data-structures concurrency queue


    【解决方案1】:

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

    来自 BlockingQueue 的文档:

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

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

    【讨论】:

    • 谢谢乔恩 - 我没有注意到这一点。那么在哪里/为什么要使用 ConcurrentLinkedQueue?
    • 当你需要从很多线程访问队列,但你不需要“等待”它。
    • 如果您的线程正在检查多个队列,ConcurrentLinkedQueue 也很有用。例如,在多租户服务器中。假设出于隔离原因,您不使用单个阻塞队列和租户鉴别器。
    • 只有当我们使用 bounded 队列时,您的情况才成立,在 unbounded 队列中 take()put() 只会消耗额外的资源(在同步)比ConcurrentLinkedQueue 。虽然在生产者-消费者场景中使用有界队列
    • @Adamski IMO,ConcurrentLinkedQueue 只是一个用于多线程环境的链表。最好的类比是 ConcurrentHashMap 和 HashMap。
    【解决方案2】:

    这个问题值得更好的回答。

    Java 的ConcurrentLinkedQueue 基于著名的algorithm by Maged M. Michael and Michael L. Scott 用于non-blocking lock-free 队列。

    “非阻塞”在这里作为竞争资源(我们的队列)的术语意味着无论平台的调度程序做什么,比如中断一个线程,或者如果有问题的线程太慢,其他线程都在竞争相同的资源仍将能够进行。例如,如果涉及锁,则持有锁的线程可能会被中断,并且等待该锁的所有线程都将被阻塞。 Java 中的内在锁(synchronized 关键字)也会对性能造成严重影响——比如当涉及到biased locking 并且您确实存在争用时,或者在虚拟机决定在自旋宽限期后“膨胀”锁之后并阻塞竞争线程......这就是为什么在许多情况下(低/中竞争的场景),对原子引用进行比较和设置可以更有效,这正是许多非阻塞数据结构正在做的事情.

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

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

    【讨论】:

    • 根据您的最后一段,您为什么说在队列中等待是设计并发系统的一种糟糕方式?如果我们有一个线程组有 10 个线程从任务队列中吃任务,当任务队列少于 10 个任务时阻塞有什么问题?
    • @AlexandruNedelcu 你不能做出像“非常糟糕”这样的笼统声明,因为你说要使用的演员框架通常使用你自己BlockingQueue的线程池。如果您需要一个高反应性的系统,并且您知道如何处理背压(阻塞队列可以缓解的问题),那么非阻塞显然更胜一筹。但是.. 阻塞 IO 和阻塞队列通常可以胜过非阻塞,特别是如果您有长时间运行的任务,这些任务是 IO 绑定的并且不能被除数征服。
    • @AdamGent - Actor 框架确实有基于阻塞队列的邮箱实现,但在我看来这是一个错误,因为阻塞不能在异步边界上工作,因此只能在演示中工作。对我来说,这一直是令人沮丧的根源,例如 Akka 处理溢出的概念是阻止而不是丢弃消息,直到版本 2.4,即尚未发布。也就是说,我不相信阻塞队列可以更好的用例。您还将两件不应该混为一谈的事情混为一谈。我还没有谈到阻塞 I/O。
    • @AlexandruNedelcu 虽然我一般同意你的背压,但我还没有看到从上到下的“无锁”系统。在技​​术堆栈的某个地方,无论是 Node.js、Erlang、Golang,它都使用某种等待策略,无论是阻塞队列(锁)还是 CAS 旋转阻塞,在某些情况下,传统的锁策略更快。由于一致性,很难没有锁,这对于阻塞 io 和调度程序尤其重要,它们是〜生产者/消费者。 ForkJoinPool 适用于短时间运行的任务,它仍然具有 CAS 旋转锁。
    • @AlexandruNedelcu 我想如果你能告诉我如何使用 ConcurrentLinkedQueue (这不是有界的顺便说一句,因此我的弱背压参数)生产者/消费者模式,这是调度程序和线程池所需的模式我我想我会屈服并承认 BlockingQueue 永远不应该被使用(而且你不能欺骗并委托给其他人做调度,即 akka,因为它反过来会做阻塞/等待,因为它是一个生产者/消费者)。
    【解决方案3】:

    LinkedBlockingQueue 在队列为空或已满且相应的消费者/生产者线程进入睡眠状态时阻塞消费者或生产者。但是这种阻塞特性是有代价的:每个 put 或 take 操作都会在生产者或消费者(如果有的话)之间竞争锁,因此在有许多生产者/消费者的场景中,操作可能会更慢。

    ConcurrentLinkedQueue 没有使用锁,但CAS 在其添加/轮询操作中可能会减少与许多生产者和消费者线程的争用。但是作为一个“无等待”的数据结构,ConcurrentLinkedQueue 为空时不会阻塞,这意味着消费者需要通过“忙等待”来处理返回的null 值,例如消费者线程占用 CPU。

    所以哪个“更好”取决于消费者线程的数量、它们消耗/生产的速率等。每个场景都需要一个基准。

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

    【讨论】:

    • 这里有一个疑问。正如您提到的,当队列为空时,消费者会等待......它等待多长时间。谁会通知它不要等待?
    • @brindal 据我所知,它等待的唯一方法是循环。这是一个重大问题,在这里的答案中没有引起太多关注。只是运行一个循环等待数据会占用大量处理器时间。当你的粉丝开始活跃时,你就会知道。唯一的补救措施是在循环中设置睡眠。因此,在数据流不一致的系统中,这是一个问题。也许我误解了 AlexandruNedelcu 的回答,但操作系统本身就是一个并发系统,如果它充满了非阻塞事件循环,那将是非常低效的。
    • 好的,但是如果使用unbounded blockingqueue 会比基于CAS 的并发ConcurrentLinkedQueue 更好
    • @orodbhen 睡觉也不会消除浪费。操作系统必须做很多工作才能让线程脱离睡眠并安排并运行它。如果消息尚不可用,那么您的操作系统所做的工作就会浪费掉。我建议最好使用 BlockingQueue,因为它是专门为生产者-消费者问题设计的。
    • 其实我对“消费/生产率”这部分很感兴趣,那么如果率高的话,哪个更好呢?
    【解决方案4】:

    另一个解决方案(不能很好地扩展)是集合通道:java.util.concurrent SynchronousQueue

    【讨论】:

      【解决方案5】:

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2013-06-08
        • 2016-11-16
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-02-15
        • 2013-10-25
        • 1970-01-01
        相关资源
        最近更新 更多