在多线程应用程序中,队列需要处理多个并发的生产者一消费者方案。正确选择并发队列对于 在我们的算法中实现良好性能至关重要。
BlockingQueue:
提供了一种简单的线程安全机制。在此队列中,线程需要等待队列的可用性。生产者将在添加 元素之前等待可用容量,而消费者将等待直到队列为空。为了实现这种阻塞机制, BlockingQueue接口在常规Queue函数的基础上提供了两个函数:put和take。这些功能等效 于标准Queue中的add和remove。
ArrayBlockingQueue:
此队列在内部使用数组。因此,它是一个有界队列,这意味着它具有固定的大小。适合生产者 /消费者比率通常很低情况,我们将耗时的任务分配给多个worker。由于此队列不能无限增长, 因此如果出现内存问题,需要将大小限制将作为安全阈值。
ArrayBlockingQueue对put和take操作都使用一个锁。这样可以确保不覆盖条目,但会降低 性能。
LinkedBlockingQueue:
LinkedBlockingQueue使用链表变体,其中每个队列项目是一个新的节点。虽然这使队列在原则上不受限制,但仍然具有Integer.MAX_VALUE的硬限制。我们可以使用构造函数 LinkedBlockingQueue (int capacity) 设置队列大小。
队列使用不同的锁进行put和take操作。因此两种操作可以并行完成并提高了吞吐量。
由于LinkedBlockingQueue可以是有界的或无界的,为什么我们还要使用 ArrayBlockingQueue?每次在队列中添加或删除项目时,LinkedBlockingQueue都需要分配和 取消分配节点。因此,如果队列快速增长和快速收缩,则ArrayB1ockingQueue可能是更好的 选择。
据说LinkedBlockingQueue的性能是不可预测的。换句话说,我们始终需要剖析我们的方案以 确保我们使用正确的数据结构。
PriorityBlockingQueue:
当我们需要按特定顺序消费数据时,PriorityB1ockingQueue是我们的首选解决方案。为此, PriorityBlockingQueue使用基于数组的二进制堆。
尽管在内部使用单个锁定机制,但是take操作可以与put操作同时进行。使用简单的自旋锁 可以实现这一点。
一个典型的用例是使用具有不同优先级的任务。我们不希望低优先级的任务代替高优先级的任务。
DelayQueue:
当使用者只能take过期的数据项目时,我们使用DelayQueue。有趣的是,它在内部使用 PriorityQueue来按数据项目的到期时间对其进行排序。
由于这不是通用队列,因此它无法涵盖ArrayBlockingQueue或LinkedBlockingQueue那样多的场景。例如,我们可以使用此队列来实现一个简单的事件循环,类似于在NodeJS中找到的 事件循环。我们将异步任务放在队列中,以便在它们到期时进行后续处理。
LinkedTransferQueue:
LinkedTransferQueue引入一个transfer方法。尽管其他队列通常在生产或消费数据项目时阻塞,但LinkedTransferQueue允许生产者等待数据项目的消费。
当我们需要保证放入队列中的某个特定项目已被消费者take拿走时,可以使用 LinkedTransferQueue。同样,我们可以使用此队列实现简单的反压算法。实际上,通过阻止 生产者直到消费,消费者可以驱动所产生的消息流。
SynchronousQueue:
普通队列通常包含许多数据项目,但SynchronousQueue最多始终只有一个项目。换句话说, 我们需要将SynchronousQueue视为在两个线程之间交换某些数据的简单方法。
当我们有两个需要访问共享状态的线程时,我们通常将它们与CountDownLatch或其他同步机 制同步。通过使用SynchronousQueue,我们可以避免线程的这种手动同步。
ConcurrentLinkedQueue:
ConcurrentLinkedQueue是本文唯一的非阻塞队列,因此,它提供了一种"免等待”算法,其中add和poll保证是线程安全的,并立即返回。该队列使用CAS (Compare-And-Swap)代替锁。
在内部,它基于Maged M.Michael和Michael L.Scott的简单,快速和实用的非阻塞和阻塞 并发队列算法。
Was this helpful?
0 / 0