JUC系列并发容器之阻塞队列Overview

Posted 顧棟

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JUC系列并发容器之阻塞队列Overview相关的知识,希望对你有一定的参考价值。

阻塞队列

文章目录

  • 什么是阻塞队列

  • 阻塞队列的主要成员和关系

  • 使用场景

  • 阻塞队列的线程安全性

  • 消费者阻塞

当队列为空时,消费者没有数据可以消费,则消费者队列会阻塞(挂起),直到有资源可以可以被消费,消费者线程会被自动唤醒并消费。

  • 生产者阻塞

当队列已满没有可用空间时,生产者线程会被阻塞(挂起),知道队列有空闲位置腾出时,自动唤醒并生产数据。

BlockingQueue


一个队列,它还支持在检索元素时等待队列变为非空,并在存储元素时等待队列中的空间变为可用的操作。

BlockingQueue 方法有四种形式,用不同的方式处理不能立即满足,但可能在未来某个时间点满足的操作:

  • 抛出异常:如果试图的操作无法立即执行,抛一个异常。

  • 返回特殊值(null 或 false,取决于 操作):如果试图的操作无法立即执行,返回一个特定的值(常常是 true / false)。

  • 一直阻塞:如果试图的操作无法立即执行,无限期地阻塞当前线程,直到队列可用或者响应中断。

  • 超时退出:如果试图的操作无法立即执行,阻塞当前线程,直到给定的最大时间限制才放弃。返回一个特定值以告知该操作是否成功(典型的是 true / false)。

下表总结了这些方法:

Throws exceptionSpecial valueBlocksTimes out
Insertadd(e)offer(e)put(e)offer(e, time, unit)
Removeremove()poll()take()poll(time, unit)
Examineelement()peek()不可用不可用

BlockingQueue 不接受空元素。 在尝试添加、放置或提供 null 时,实现会抛出 NullPointerException。 null 用作标记值以指示轮询操作失败。

BlockingQueue 可能是容量受限的。在任何给定时间,它都可能有一个剩余容量,超出该容量就不能在不阻塞的情况下放置其他元素。没有任何内在容量限制的 BlockingQueue 总是报告 Integer.MAX_VALUE 的剩余容量。

BlockingQueue 实现主要用于生产者-消费者队列,但还支持 Collection 接口。因此,例如,可以使用 remove(x) 从队列中删除任意元素。但是,此类操作通常不会非常有效地执行,并且仅用于偶尔使用,例如当取消排队的消息时。

BlockingQueue 实现是线程安全的。所有排队方法都使用内部锁或其他形式的并发控制以原子方式实现其效果。但是,批量收集操作 addAll、containAll、retainAll 和 removeAll 不一定以原子方式执行,除非在实现中另外指定。因此,例如,addAll© 在仅添加 c 中的一些元素后可能会失败(抛出异常)。

BlockingQueue 本质上不支持任何类型的“关闭”或“关闭”操作来指示不再添加项目。此类功能的需求和使用往往取决于实现。例如,一种常见的策略是生产者插入特殊的流尾或抑制对象,当消费者采用时会相应地解释这些对象。

BlockingDeque


一个双向队列,Deque 还支持阻塞操作,即在检索元素时等待双端队列变为非空,并在存储元素时等待双端队列中的空间可用。

BlockingDeque 方法有四种形式,用不同的方式处理不能立即满足但可能在未来某个时间点满足的操作:

  • 抛出异常:如果试图的操作无法立即执行,抛一个异常。

  • 返回特殊值(null 或 false,取决于 操作):如果试图的操作无法立即执行,返回一个特定的值(常常是 true / false)。

  • 一直阻塞:如果试图的操作无法立即执行,无限期地阻塞当前线程,直到队列可用或者响应中断。

  • 超时退出:如果试图的操作无法立即执行,阻塞当前线程,直到给定的最大时间限制才放弃。返回一个特定值以告知该操作是否成功(典型的是 true / false)。

下表总结了这些方法:

头部元素

Throws exceptionSpecial valueBlocksTimes out
InsertaddFirst(e)offerFirst(e)putFirst(e)offerFirst(e, time, unit)
RemoveremoveFirst()pollFirst()takeFirst()pollFirst(time, unit)
ExaminegetFirst()peekFirst()不可用不可用

尾部元素

Throws exceptionSpecial valueBlocksTimes out
InsertaddLast(e)offerLast(e)putLast(e)offerLast(e, time, unit)
RemoveremoveLast()pollLast()takeLast()pollLast(time, unit)
ExaminegetLast()peekLast()不可用不可用

与任何 BlockingQueue 一样,BlockingDeque 是线程安全的,不允许空元素,并且可能(或可能不会)受容量限制。

BlockingQueue对比BlockingDeque

BlockingDeque 实现可以直接用作 FIFO BlockingQueue。 从 BlockingQueue 接口继承的方法与 BlockingDeque 方法完全相同,如下表所示:

BlockingQueueBlockingDeque
add(e)addLast(e)
offer(e)offerLast(e)
put(e)putLast(e)
offer(e, time, unit)offerLast(e, time, unit)
remove()removeFirst()
poll()pollFirst()
take()takeFirst()
poll(time, unit)pollFirst(time, unit)
element()getFirst()
peek()peekFirst()

内存一致性效果:与其他并发集合一样,线程中的操作在将对象放入BlockingDeque happen-before在另一个线程中从 BlockingDeque 访问或删除该元素之后的操作。

TransferQueue

生产者可以在其中等待消费者接收元素的 BlockingQueue。 TransferQueue 例如在消息传递应用程序中可能很有用,其中生产者有时(使用方法传输)等待消费者调用 take 或 poll 接收元素,而在其他时候(通过方法 put)将元素排入队列而不等待接收。 tryTransfer 的非阻塞和超时版本也可用。 还可以通过hasWaitingConsumer 查询 TransferQueue 是否有线程在等待项目,这与 peek 操作相反。

与其他阻塞队列一样,TransferQueue 可能是容量受限的。 如果是这样,则尝试的传输操作可能最初会阻塞等待可用空间,和/或随后会阻塞等待消费者接收。 请注意,在容量为零的队列中,例如 SynchronousQueue,put 和 transfer 实际上是同义词。

阻塞队列实现

ArrayBlockingQueue 数组阻塞队列

基于数组结构的有界阻塞队列,此队列按照FIFO原则对元素进行排序。在构造ArrayBlockingQueue对象时,会确定队列的大小,一旦确定就无法修改。

DelayQueue 延迟队列

基于优先级队列实现的无界阻塞队列

在创建元素的时候,可以指需要多久才能从队列中获取当前元素,只有在延迟期满的情况下,才能从队列中提取元素。

LinkedBlockingQueue 链阻塞队列

基于链表结构的有界阻塞队列,链表的默认和最大长度是 Integer.MAX_VALUE.

PriorityBlockingQueue 优先级的阻塞队列

支持优先级排序的无界阻塞队列

默认情况下,采用元素自然升序排序,也可以定义实现compareTo()方法来指定元素排序规则,或者在初始化PriorityBlockingQueue时,指定Comparator来对元素进行排序,需要注意的是,无法保证同优先级下的元素顺序,同时从一个 PriorityBlockingQueue 获得一个 Iterator 的话,该 Iterator 并不能保证它对元素的遍历是以优先级为序的。

SynchronousQueue 同步队列

用于控制互斥操作的阻塞队列

SynchronousQueue 是一个特殊的队列,它的内部同时只能够容纳单个元素。每一个put操作需要等待一个take操作,否则不能继续添加元素。

这个队列本身不存储任何元素。非常适合传递性场景。SynchronousQueue 的吞吐量高于LinkedBlockingQueue和ArrayBlockingQueue。

LinkedBlockingDeque 链阻塞双端队列

基于链表结构实现的双向阻塞队列

双端队列是一个你可以从任意一端插入或者抽取元素的队列。

LinkedTransferQueue 链式传输队列

基于链表结构实现的无界阻塞队列

transfer

如果有消费者正在等待接受元素,transfer方法可以把生产者传入的元素立即传输给消费者,如果没有消费者在等待元素,transfer会将元素添加到队列的尾结点,并等到该元素被消费者消费了才返回。

trytransfer

试探生产者传入的元素是否能直接传给消费者,如果没有消费者等待接收元素,则返回false。

两者的区别是trytransfer无论消费者是否接收,方法立即返回,transfer需要等待消费者消费了才返回。

以上是关于JUC系列并发容器之阻塞队列Overview的主要内容,如果未能解决你的问题,请参考以下文章

并发编程实践之公平有界阻塞队列实现

JUC并发编程 共享模式之工具 线程池 -- 自定义线程池(阻塞队列)

并发编程-阻塞队列&JUC常用工具

JUC系列并发容器之CopyOnWrite(CopyOnWriteArrayListCopyOnWriteArraySet)

JUC系列并发容器之ConcurrentHashMap(JDK1.8版)

JUC并发编程--- 阻塞队列和同步队列使用