我应该在 Java 中使用哪个并发 Queue 实现?

来自 JavaDocs:

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

我有两个场景,一个需要队列来支持许多生产者(使用它的线程)和一个消费者,另一个是反过来的。

我不知道该使用哪个实现。有人能解释一下它们之间的区别吗?

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

128374 次浏览

你的问题题目提到了阻塞队列。然而,ConcurrentLinkedQueue没有一个阻塞队列。

BlockingQueueArrayBlockingQueueDelayQueueLinkedBlockingDequeLinkedBlockingQueuePriorityBlockingQueueSynchronousQueue

其中一些显然不适合您的用途(DelayQueuePriorityBlockingQueueSynchronousQueue)。LinkedBlockingQueueLinkedBlockingDeque是相同的,只是后者是一个双端队列(它实现了 Deque 接口)。

由于 ArrayBlockingQueue只有在您想要限制元素数量时才有用,因此我坚持使用 LinkedBlockingQueue

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

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

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

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

ConcurrentLinkedQueue 意味着不采用锁(即不进行同步(this)或 锁,锁调用)。在修改期间,它将使用一个 CAS-比较与交换操作来查看头/尾节点是否仍然与启动时相同。如果是,则操作成功。如果头/尾节点不同,它将旋转并重试。

LinkedBlockingQueue 将在任何修改之前获取锁。所以你的报价电话会被屏蔽,直到他们拿到锁。您可以使用占用 TimeUnit 的服务重载来表明您只愿意在放弃添加之前等待 X 段时间(通常适用于消息类型队列,其中消息在 X 个毫秒之后失效)。

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

至于用哪个,要看情况了。我倾向于使用 并发 LinkedQueue,因为生产者将工作放入队列所需的时间是多种多样的。我没有很多制片人在同一时间制作。但消费者方面则更为复杂,因为投票不会进入良好的睡眠状态。你得自己处理。

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

  1. SynchronousQueue(取自另一个 有个问题)

SynchronousQueue更像是一种切换,而 LinkedBlockingQueue只允许单个元素。不同之处在于,对 SynchronousQueueput()调用在有相应的 take()调用之前不会返回,但是对于大小为1的 LinkedBlockingQueueput()调用(对空队列)将立即返回。它实际上是 BlockingQueue实现,用于在不需要队列时(不需要维护任何挂起的数据)。

  1. LinkedBlockingQueue(LinkedList实现但不完全是 JDK 实现的 LinkedList它使用静态内部类 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 (Array 实现)

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();
}

从构造函数来看,ArrayBlockingQueueLinkedBlockingQueue最大的区别是 底层数据结构 Array 和其他 linkedList

ArrayBlockingQueue使用 单锁双条件算法LinkedBlockingQueue是“两个锁队列”算法的变体,它有2个锁2个条件(take Lock,putLock)

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