【问题标题】:Which concurrent Queue implementation should I use in Java?我应该在 Java 中使用哪个并发队列实现?
【发布时间】:2010-11-21 01:30:32
【问题描述】:

来自 JavaDocs:

  • ConcurrentLinkedQueue 是当许多线程将共享对公共集合的访问时的合适选择。此队列不允许空元素。
  • ArrayBlockingQueue 是一个经典的“有界缓冲区”,其中一个固定大小的数组保存由生产者插入并由消费者提取的元素。此类支持对等待的生产者和消费者线程进行排序的可选公平策略
  • LinkedBlockingQueue 通常具有比基于数组的队列更高的吞吐量,但在大多数并发应用程序中的可预测性能较差。

我有 2 个场景,一个需要队列支持多个生产者(使用它的线程)和一个消费者,另一个则相反。

我不明白要使用哪个实现。有人能解释一下有什么区别吗?

另外,ArrayBlockingQueue 中的“可选公平政策”是什么?

【问题讨论】:

  • 您也忘了询问 PriorityBlockingQueue,这对于指定线程处理的顺序很有用。

标签: java multithreading concurrency queue


【解决方案1】:
  1. SynchronousQueue(取自另一个question)

SynchronousQueue 更像是一种交接,而LinkedBlockingQueue 只允许单个元素。区别在于 put() 对 SynchronousQueue 的调用将不会返回,直到有相应的 take() 调用,但是对于大小为 1 的 LinkedBlockingQueue,put() 调用(对空队列)将返回立即地。它本质上是 BlockingQueue 实现,当您真的不想要队列时(您不想维护任何待处理的数据)。

  1. LinkedBlockingQueue (LinkedList 实现但不完全是LinkedList的JDK实现@它使用静态内部类Node来维护元素之间的链接)

LinkedBlockingQueue 的构造函数

public LinkedBlockingQueue(int capacity) 
{
        if (capacity < = 0) throw new IllegalArgumentException();
        this.capacity = capacity;
        last = head = new Node< E >(null);   // Maintains a underlying linkedlist. ( Use when size is not known )
}

用于维护链接的节点类

static class Node<E> {
    E item;
    Node<E> next;
    Node(E x) { item = x; }
}

3 . ArrayBlockingQueue(数组实现)

ArrayBlockingQueue 的构造函数

public ArrayBlockingQueue(int capacity, boolean fair) 
{
            if (capacity < = 0)
                throw new IllegalArgumentException();
            this.items = new Object[capacity]; // Maintains a underlying array
            lock = new ReentrantLock(fair);
            notEmpty = lock.newCondition();
            notFull =  lock.newCondition();
}

恕我直言,ArrayBlockingQueue 和 LinkedBlockingQueue 之间的最大区别从构造函数中可以清楚地看出,构造函数具有底层数据结构 Array 和其他linkedList。

ArrayBlockingQueue 使用 single-lock double condition algorithm 和 LinkedBlockingQueue 是“双锁队列”算法的变体,它有 2 个锁 2 个条件(takeLock,putLock)

【讨论】:

    【解决方案2】:

    基本上它们之间的区别在于性能特征和阻塞行为。

    先取最简单的,ArrayBlockingQueue 是一个固定大小的队列。因此,如果您将大小设置为 10,并尝试插入第 11 个元素,则插入语句将阻塞,直到另一个线程删除一个元素。公平问题是如果多个线程同时尝试插入和删除(换句话说,在队列被阻塞期间)会发生什么。公平算法确保第一个请求的线程是第一个获得的线程。否则,给定线程可能会比其他线程等待更长的时间,从而导致不可预知的行为(有时一个线程只需几秒钟,因为稍后启动的其他线程首先得到处理)。代价是管理公平性需要开销,从而降低吞吐量。

    LinkedBlockingQueue 和ConcurrentLinkedQueue 之间最重要的区别是,如果您从LinkedBlockingQueue 请求一个元素并且队列为空,您的线程将等待直到那里有东西。 ConcurrentLinkedQueue 将立即以空队列的行为返回。

    哪一个取决于您是否需要阻止。在你有许多生产者和一个消费者的地方,听起来很像。另一方面,如果您有许多消费者而只有一个生产者,您可能不需要阻塞行为,并且可能很乐意让消费者检查队列是否为空,如果是则继续前进。

    【讨论】:

    • 答案具有误导性。 LinkedBlockingQueue 和 ConcurrentLinkedQueue 都有方法“poll()”,它删除队列头部或返回 null(不阻塞)和方法“offer(E e)”,它插入队列尾部并且不阻塞。不同之处在于,只有 LinkedBlockingQueue 具有阻塞操作除了非阻塞操作 - 并且对于该特权,您支付 LinkedBlockingQueue 实际上具有一些锁定的代价。另一个答案解释了这一点。
    【解决方案3】:

    ConcurrentLinkedQueue 是无锁的,LinkedBlockingQueue 不是。每次调用 LinkedBlockingQueue.put() 或 LinkedBlockingQueue.take() 时,都需要先获取锁。换句话说,LinkedBlockingQueue 的并发性很差。如果您关心性能,请尝试 ConcurrentLinkedQueue + LockSupport。

    【讨论】:

      【解决方案4】:

      您的问题标题提到了阻塞队列。但是,ConcurrentLinkedQueue不是阻塞队列。

      BlockingQueues 是 ArrayBlockingQueue、DelayQueue、LinkedBlockingDeque、LinkedBlockingQueue、PriorityBlockingQueue 和 SynchronousQueue。

      其中一些显然不适合您的目的(DelayQueue、PriorityBlockingQueue 和 SynchronousQueue)。 LinkedBlockingQueue 和 LinkedBlockingDeque 是相同的,只是后者是一个双端队列(它实现了 Deque 接口)。

      由于ArrayBlockingQueue 仅在您想限制元素数量时才有用,所以我会坚持使用LinkedBlockingQueue。

      【讨论】:

      • 我从标题中删除了 Blocking 字样,谢谢。让我看看我明白了吗,你说的意思是 LinkedBlockingQueue 可以在同一个对象的多个消费者/生产场景中使用?
      • 我认为 ArrayBlockingQueue 允许更细粒度的线程排序?因此它的优势。
      【解决方案5】:

      ArrayBlockingQueue 具有较低的内存占用,它可以重用元素节点,不像 LinkedBlockingQueue 必须为每个新插入创建一个 LinkedBlockingQueue$Node 对象。

      【讨论】:

      • 好点!我更喜欢 ArrayBlockingQueue 而不是 LinkedBlockingQueue
      • 这不一定是真的 - 如果你的队列很多时候都接近空但需要能够变大,ArrayBlockingQueue 的内存占用会更差 - 它仍然有一个大数组一直分配在内存中,而LinkedBlockingQueue 在接近空时的内存占用可以忽略不计。
      【解决方案6】:

      ConcurrentLinkedQueue 表示没有锁被占用(即没有 synchronized(this) 或 Lock.lock 调用)。它将在修改期间使用CAS - Compare and Swap 操作来查看头/尾节点是否仍与启动时相同。如果是,则操作成功。如果头/尾节点不同,它会旋转并重试。

      LinkedBlockingQueue 将在任何修改之前锁定。因此,您的报价电话将被阻止,直到他们获得锁定为止。您可以使用带有 TimeUnit 的 offer 重载表示您只愿意等待 X 时间再放弃添加(通常适用于消息在 X 毫秒后过时的消息类型队列)。

      公平意味着 Lock 实现将保持线程有序。这意味着如果线程 A 进入然后线程 B 进入,线程 A 将首先获得锁。没有公平,真正发生的事情是不确定的。它很可能是被安排的下一个线程。

      至于使用哪一个,这取决于。我倾向于使用ConcurrentLinkedQueue,因为我的制作人将工作放入队列所需的时间是多种多样的。我没有很多制作人在同一时间制作。但是消费者方面更复杂,因为 poll 不会进入良好的睡眠状态。你必须自己处理。

      【讨论】:

      • 在什么情况下ArrayBlockingQueue优于LinkedBlockingQueue?
      • @akapelko ArrayBlockingQueue 允许更细粒度的排序。
      • 这是什么意思 - “它会旋转并重试。” ?
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-09-26
      • 1970-01-01
      • 2011-01-29
      • 1970-01-01
      • 2013-11-02
      • 1970-01-01
      相关资源
      最近更新 更多