Java中的并发队列指南

评论 0 浏览 0 2020-08-16

1.概述

在本教程中,我们将介绍Java中并发队列的一些主要实现。关于队列的一般介绍,请参考我们的Java 队列接口文章。

2.队列

在多线程的应用中,队列需要处理多个并发的生产者-消费者的情况。正确选择一个并发队列可能是我们的算法取得良好性能的关键。

首先,我们将看到阻塞队列和非阻塞队列的一些重要区别。然后,我们将看一下一些实现和最佳实践。

2.阻塞式与非阻塞式队列

BlockingQueue提供一种简单的线程安全机制。在这个队列中,线程需要等待队列的可用性。生产者在添加元素之前会等待可用的容量,而消费者则会等待,直到队列为空。在这些情况下,非阻塞队列将抛出一个异常或返回一个特殊的值,如nullfalse

为了实现这种阻塞机制,BlockingQueue接口在正常的Queue函数之上暴露了两个函数。puttake。这些函数相当于标准Queue中的addremove

3.并发的Queen实现

3.1. ArrayBlockingQueue

正如它的名字所示,这个队列在内部使用一个数组。因此,它是个有界队列,意味着它有一个固定的大小

一个简单的工作队列就是一个用例。这种情况通常是生产者与消费者的比例很低,我们将耗时的任务分给多个工作者。由于这个队列不能无限地增长,如果内存是个问题,大小限制就会成为一个安全阈值

说到内存,需要注意的是,队列会预先分配阵列。虽然这可能会提高吞吐量,但它也可能会消耗超过必要的内存。例如,一个大容量的队列可能会在很长一段时间内保持空白。

另外,ArrayBlockingQueueputtake操作都使用一个锁。这确保了条目不会被覆盖,但代价是性能受到影响。

3.2. LinkedBlockingQueue

LinkedBlockingQueue使用LinkedList变体,其中每个队列项目是一个新的节点。虽然这使得队列原则上是无界的,但它仍然有一个Integer.MAX_VALUE的硬限制。

另一方面,我们可以通过使用构造函数LinkedBlockingQueue(int capacity)来设置队列的大小。

这个队列为puttake操作使用不同的锁。因此,这两个操作可以并行进行,提高了吞吐量。

既然LinkedBlockingQueue可以是有界或无界的,为什么我们要用ArrayBlockingQueue而不是这个呢?LinkedBlockingQueue需要在每次从队列中添加或移除项目时分配和删除节点。由于这个原因,如果队列快速增长和快速收缩,ArrayBlockingQueue可以是一个更好的选择。

据说LinkedBlockingQueue的性能是不可预测的。换句话说,我们总是需要对我们的场景进行剖析,以确保我们使用正确的数据结构。

3.3. PriorityBlockingQueue

PriorityBlockingQueue是我们的首选解决方案当我们需要按特定顺序消费项目时。为了实现这一点,PriorityBlockingQueue使用了一个基于数组的二进制堆。

虽然在内部它使用一个单一的锁机制,但的操作可以与put操作同时发生。使用一个简单的自旋锁使这成为可能。

一个典型的用例是消耗具有不同优先级的任务。我们不希望一个低优先级的任务取代一个高优先级的任务

3.4 DelayQueue

我们使用一个延迟队列,当一个消费者只能拿一个过期的物品时。有趣的是,它在内部使用了一个PriorityQueue来按过期的项目排序。

由于这不是一个通用的队列,它不能像ArrayBlockingQueueLinkedBlockingQueue那样涵盖很多场景。例如,我们可以使用这个队列来实现一个类似于NodeJS中的简单事件循环。我们将异步任务放在队列中,以便在它们过期后进行处理。

3.5. LinkedTransferQueue

LinkedTransferQueue引入了一个transfer方法。当其他队列在生产或消费项目时通常会阻塞,而LinkedTransferQueue允许生产者等待项目的消费

当我们需要保证我们放在队列中的某个项目已经被别人拿走时,我们会使用LinkedTransferQueue。另外,我们可以使用这个队列实现一个简单的反压算法。事实上,通过阻断生产者直到消费,消费者可以驱动生产的消息流

3.6. SynchronousQueue

虽然队列通常包含许多项目,但SynchronousQueue总会有,最多就是一个项目。换句话说,我们需要把SynchronousQueue看作是在两个线程之间交换一些数据的简单方法

当我们有两个线程需要访问一个共享状态时,我们经常用CountDownLatch或其他同步机制来同步这些线程。通过使用SynchronousQueue,我们可以避免这种线程的手动同步

3.7. ConcurrentLinkedQueue

ConcurrentLinkedQueue是本指南的唯一非阻塞队列。因此,它提供了一个“无等待”算法,其中addpoll被保证是线程安全的,并立即返回。这个队列使用CAS(Compare-And-Swap)来代替锁。

在内部,它基于Maged M. Michael和Michael L. Scott的Simple, Fast, and Practical Non-Blocking and Blocking Concurrent Queue Algorithms中的一种算法。

它是现代反应式系统的完美候选者,在这些系统中,使用阻塞数据结构往往是被禁止的。

另一方面,如果我们的消费者最终要在一个循环中等待,我们也许应该选择一个阻塞队列作为更好的选择。

4.总结

在本指南中,我们走过了不同的并发队列实现,讨论了它们的优势和劣势。考虑到这一点,我们就能更好地开发高效、持久、可用的系统。

最后更新2023-03-15
0 个评论
标签