【Java数据结构及算法实战】系列008:Java队列02——阻塞队列BlockingQueue

简介: 阻塞队列(BlockingQueue)是一种支持额外操作的队列

阻塞队列(BlockingQueue)是一种支持额外操作的队列,这两个附加的操作是:

l 在队列为空时,获取元素的线程会等待队列变为非空。

l 当队列满时,存储元素的线程会等待队列可用。

Java提供了java.util.concurrent.BlockingQueue接口以提供对阻塞队列的支持。该接口是Java Collections Framework的一个成员。

  1. BlockingQueue的方法

BlockingQueue对不能立即满足的操作有不同的处理方法,这些方法共有四种形式:

l 抛出一个异常

l 返回一个特殊值,根据操作的不同可能返回null或false

l 无限期地阻塞当前线程,直到操作成功

l 在放弃之前只阻塞给定的最大时间限制。

这些方法总结如下:

方法\处理方式

抛出异常

返回特殊值

一直阻塞

超时退出

插入方法

add(e)

队列未满时,返回 true;

队列满则抛出异常

offer(e)

队列未满时,返回 true;

队列满时返回 false。非阻塞立即返回。

put(e)

队列未满时,直接插入没有返回值;

队列满时会阻塞等待,一直等到队列未满时再插入。

offer(e,time,unit)

设定等待的时间,如果在指定时间内还不能往队列中插入数据则返回 false。

插入成功返回 true。

移除方法

remove()

队列不为空时,返回队首值并移除

队列为空时抛出异常

poll()

队列不为空时返回队首值并移除

队列为空时返回 null。非阻塞立即返回。

take()

队列不为空返回队首值并移除

当队列为空时会阻塞等待,一直等到队列不为空时再返回队首值。

poll(time,unit)

设定等待的时间,如果在指定时间内队列还未孔则返回 null

不为空则返回队首值

检查方法

element()

队列不为空时返回队首值但不移除

队列为空时抛出异常。

peek()

队列不为空时返回队首值但不移除

队列为空时返回 null。

不可用

不可用

BlockingQueue不接受null值,当尝试add、put或offer一个null值时,将抛出NullPointerException。null值有特殊的用意,用来表示poll操作失败。

BlockingQueue可以有容量限制,如果不指定容量则默认容量大小是Integer.MAX_VALUE。任何时候,只要超过了剩余容量(remainingCapacity),则新的元素不能被放入队列中。

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

BlockingQueue实现是线程安全的。所有排队方法都使用内部锁或其他并发控制形式自动地实现其效果。但是,除非在实现中指定了otherwise,否则批量Collection操作addAll、containsAll、retainAll和removeAll不一定是自动执行的。因此,例如,在只添加c中的某些元素之后, addAll ( c )可能会失败(抛出一个异常)。

BlockingQueue本质上不支持任何类型的close或shutdown操作,以指示不再添加任何项。这些特性的需求和使用往往依赖于具体的实现。例如,一种常见的策略是生产者插入特殊的对象(比如end-of-stream或 poison),当被消费者采用时,这些对象将得到相应的解释。

  1. BlockingQueue的使用示例

以下是一个典型的生产者-消费者场景的使用示例。注意,一个BlockingQueue可以安全地与多个生产者和多个消费者一起使用。

class Producer implements Runnable {

private final BlockingQueue queue;

Producer(BlockingQueue q) { queue = q; }

public void run() {

 try {



   while (true) { queue.put(produce()); }



 } catch (InterruptedException ex) { ... handle ...}


}

Object produce() { ... }

}

class Consumer implements Runnable {

private final BlockingQueue queue;

Consumer(BlockingQueue q) { queue = q; }

public void run() {

 try {



   while (true) { consume(queue.take()); }



 } catch (InterruptedException ex) { ... handle ...}


}

void consume(Object x) { ... }

}

class Setup {

void main() {

 BlockingQueue q = new SomeQueueImplementation();



 Producer p = new Producer(q);



 Consumer c1 = new Consumer(q);



 Consumer c2 = new Consumer(q);



 new Thread(p).start();



 new Thread(c1).start();



 new Thread(c2).start();


}

}}

  1. BlockingQueue的内存一致性影响

这种影响与其他并发集合一样,某个线程将数据元素放入BlockingQueue的操作,happen-before 于另一个线程访问或者删除BlockingQueue中该数据元素的操作。

3.1. happens-before的含义

happen-before规则用来描述两个操作之间的顺序关系,这两个操作可以再一个线程内,也可以不再一个线程内。此顺序并不严格意味着执行时间上的顺序,而是至前一个操作的结果要对后一个操作可见。

happens-Before关系的定义如下:

l 如果一个happens-before另一个操作,那么第一个操作的执行结果对第二个操作可见,而且第一个操作的执行顺序排在第二个操作之前

l 两个操作之间存在happens-before关系,并不意味着Java平台的具体实现必须按照happens-before关系指定的顺序来执行。如果重排序之后的执行结果,与按照happens-before关系来执行的结果一致,那么这种重排序并不非法。

举例来说,如果在程序执行顺序上,A先于B,并且A修改了共享变量,而B正好使用该共享变量,那么A需要happen-before B,再直白一点,就是A对共享变量的修改,需要在B执行时,对B可见。

3.2. happens-before规则

l 程序顺序规则:一个线程中的每个操作,happens-before于该线程中的任意后续操作。

l 监视器锁规则:对一个锁的解锁,happens-before于随后对这个锁的加锁。

l volatile规则:对一个volatile域的写,happens-before于任意后续对这个volatile域的读。

l 传递性:如果A happens-before B,并且B happens-before C,那么A happens-before C。

l start()规则:如果线程A执行操作ThreadB.start(),那么A线程的ThreadB.start()操作happens-before于线程B中的任意操作。

l join()规则:如果线程A执行操作ThreadB.join()并成功返回,那么线程B的任意操作happens-before于线程A从ThreadB.join()操作成功返回。

对所有这些规则的说明:A happens-before B并不意味着A一定要先在B之前发生,而是说,如果A已经发生在了B前面,那么A的操作结果一定要对B可见

  1. 参考引用

本系列归档至《Java数据结构及算法实战》:https://github.com/waylau/java-data-structures-and-algorithms-in-action
《数据结构和算法基础(Java语言实现)》(柳伟卫著,北京大学出版社出版):https://item.jd.com/13014179.html

目录
相关文章
|
18天前
|
存储 Java
【数据结构】优先级队列(堆)从实现到应用详解
本文介绍了优先级队列的概念及其底层数据结构——堆。优先级队列根据元素的优先级而非插入顺序进行出队操作。JDK1.8中的`PriorityQueue`使用堆实现,堆分为大根堆和小根堆。大根堆中每个节点的值都不小于其子节点的值,小根堆则相反。文章详细讲解了如何通过数组模拟实现堆,并提供了创建、插入、删除以及获取堆顶元素的具体步骤。此外,还介绍了堆排序及解决Top K问题的应用,并展示了Java中`PriorityQueue`的基本用法和注意事项。
23 5
【数据结构】优先级队列(堆)从实现到应用详解
|
12天前
|
Java API 容器
JAVA并发编程系列(10)Condition条件队列-并发协作者
本文通过一线大厂面试真题,模拟消费者-生产者的场景,通过简洁的代码演示,帮助读者快速理解并复用。文章还详细解释了Condition与Object.wait()、notify()的区别,并探讨了Condition的核心原理及其实现机制。
|
6天前
|
前端开发
07_用队列实现栈
07_用队列实现栈
|
6天前
|
测试技术
02_由两个栈组成的队列
02_由两个栈组成的队列
|
9天前
|
存储
|
9天前
|
大数据 UED 开发者
实战演练:利用Python的Trie树优化搜索算法,性能飙升不是梦!
在数据密集型应用中,高效搜索算法至关重要。Trie树(前缀树/字典树)通过优化字符串处理和搜索效率成为理想选择。本文通过Python实战演示Trie树构建与应用,显著提升搜索性能。Trie树利用公共前缀减少查询时间,支持快速插入、删除和搜索。以下为简单示例代码,展示如何构建及使用Trie树进行搜索与前缀匹配,适用于自动补全、拼写检查等场景,助力提升应用性能与用户体验。
27 2
|
13天前
|
算法 搜索推荐 开发者
别再让复杂度拖你后腿!Python 算法设计与分析实战,教你如何精准评估与优化!
在 Python 编程中,算法的性能至关重要。本文将带您深入了解算法复杂度的概念,包括时间复杂度和空间复杂度。通过具体的例子,如冒泡排序算法 (`O(n^2)` 时间复杂度,`O(1)` 空间复杂度),我们将展示如何评估算法的性能。同时,我们还会介绍如何优化算法,例如使用 Python 的内置函数 `max` 来提高查找最大值的效率,或利用哈希表将查找时间从 `O(n)` 降至 `O(1)`。此外,还将介绍使用 `timeit` 模块等工具来评估算法性能的方法。通过不断实践,您将能更高效地优化 Python 程序。
30 4
|
24天前
|
存储 人工智能 C语言
数据结构基础详解(C语言): 栈的括号匹配(实战)与栈的表达式求值&&特殊矩阵的压缩存储
本文首先介绍了栈的应用之一——括号匹配,利用栈的特性实现左右括号的匹配检测。接着详细描述了南京理工大学的一道编程题,要求判断输入字符串中的括号是否正确匹配,并给出了完整的代码示例。此外,还探讨了栈在表达式求值中的应用,包括中缀、后缀和前缀表达式的转换与计算方法。最后,文章介绍了矩阵的压缩存储技术,涵盖对称矩阵、三角矩阵及稀疏矩阵的不同压缩存储策略,提高存储效率。
|
26天前
|
存储 C语言
数据结构基础详解(C语言): 栈与队列的详解附完整代码
栈是一种仅允许在一端进行插入和删除操作的线性表,常用于解决括号匹配、函数调用等问题。栈分为顺序栈和链栈,顺序栈使用数组存储,链栈基于单链表实现。栈的主要操作包括初始化、销毁、入栈、出栈等。栈的应用广泛,如表达式求值、递归等场景。栈的顺序存储结构由数组和栈顶指针构成,链栈则基于单链表的头插法实现。
151 3
|
28天前
|
Java
【数据结构】栈和队列的深度探索,从实现到应用详解
本文介绍了栈和队列这两种数据结构。栈是一种后进先出(LIFO)的数据结构,元素只能从栈顶进行插入和删除。栈的基本操作包括压栈、出栈、获取栈顶元素、判断是否为空及获取栈的大小。栈可以通过数组或链表实现,并可用于将递归转化为循环。队列则是一种先进先出(FIFO)的数据结构,元素只能从队尾插入,从队首移除。队列的基本操作包括入队、出队、获取队首元素、判断是否为空及获取队列大小。队列可通过双向链表或数组实现。此外,双端队列(Deque)支持两端插入和删除元素,提供了更丰富的操作。
30 0
【数据结构】栈和队列的深度探索,从实现到应用详解
下一篇
无影云桌面