阻塞队列一——java中的阻塞队列
目录
- 阻塞队列简介:介绍阻塞队列的特性与应用场景
- java中的阻塞队列:介绍java中实现的供开发者使用的阻塞队列
BlockQueue中方法:介绍阻塞队列的API接口- 阻塞队列的实现原理:具体的例子说明阻塞队列的实现原理
- 总结
阻塞队列简介
阻塞队列(BlockingQueue)首先是一个支持先进先出的队列,与普通的队列完全相同;
其次是一个支持阻塞操作的队列,即:
- 当队列满时,会阻塞执行插入操作的线程,直到队列不满。
- 当队列为空时,会阻塞执行获取操作的线程,直到队列不为空。
阻塞队列用在多线程的场景下,因此阻塞队列使用了锁机制来保证同步,这里使用的可重入锁;
而对于阻塞与唤醒机制则有与锁绑定的Condition实现
应用场景:生产者消费者模式
java中的阻塞队列
java中的阻塞队列根据容量可以分为有界队列和无界队列:
- 有界队列:队列中只能存储有限个元素,超出后存放元素线程会被阻塞或者失败。
- 无界队列:队列中可以存储无限个元素。
java8中提供了7种阻塞队列阻塞队列供开发者使用,如下表:
| 类名 | 描述 |
|---|---|
| ArrayBlockingQueue | 一个由数组结构组成的有界阻塞队列 |
| LinkedBlockingQueue | 由链表结构组成的有界阻塞队列(默认大小Integer.MAX_VALUE) |
| PriorityBlockingQueue | 支持优先级排序的无界阻塞队列 |
| DelayQueue | 使用优先级队列实现的延迟无界阻塞队列 |
| SynchronousQueue | 不存储元素的阻塞队列,即单个元素的队列 |
| LinkedTransferQueue | 由链表结构组成的无界阻塞队列 |
| LinkedBlockingDeque | 由链表结构组成的双向阻塞队列 |
另外还有一个在ScheduledThreadPoolExecutor中实现的DelayedWorkQueue阻塞队列,
但这个阻塞队列开发者不能使用。它们之间的UML类图如下图:

BlockingQueue接口是阻塞队列对外的访问接口,所有的阻塞队列都实现了BlockQueue中的方法
BlockQueue中方法
作为一个队列的核心方法就是入队和出队。由于存在阻塞策略,BlockQueue将出队入队的情况分为了四组,每组提供不同的方法:
抛出异常:当队列满时,如果再往队列中插入元素,则抛出
IllegalStateException异常;
当队列为空时,从队列中获取元素则抛出NoSuchElementException异常。返回特定值(布尔值):当队列满时,如果再往队列中插入元素,则返回false;当队列为空时,从队列中获取元素则返回null。
一直阻塞:当队列满时,如果再往队列中插入元素,阻塞当前线程直到队列中至少一个被移除或者响应中断退出;
当队列为空时,则阻塞当前线程直到至少一个元素元素入队或者响应中断退出。超时退出:当队列满时,如果再往队列中插入元素,阻塞当前线程直到队列中至少一个被移除或者达到指定的等待时间退出或者响应中断退出;
当队列为空时,则阻塞当前线程直到至少一个元素元素入队或者达到指定的等待时间退出或者响应中断退出。
对于每种情况BlockingQueue提供的方法如下表:
| 方法\处理方式 | 抛出异常 | 返回特定值(布尔值) | 一直阻塞 | 超时退出 |
|---|---|---|---|---|
| 插入 | add(e) | offer(e) | put(e) | offer(e,time,unit) |
| 移除 | remove() | poll() | take() | poll(time.unit) |
| 检查 | element() | peek() | 不可用 | 不可用 |
上述方法一般用于生产者-消费者模型中,是其中的生产和消费操作队列的核心方法。
除了这些方法,BlockingQueue还提供了一些其他的方法如下表:
| 方法名称 | 描述 |
|---|---|
| remove(Object o) | 从队列中移除一个指定值 |
| size() | 获取队列中元素的个数 |
| contains(Object o) | 判断队列是否包含指定的元素,但是这个元素在这次判断完可能就会被消费 |
| drainTo(Collection<? super E> c) | 将队列中元素放在给定的集合中,并返回添加的元素个数 |
| drainTo(Collection<? super E> c, int maxElements) | 将队列中元素取maxElements(不超过队列中元素个数)个放在给定的集合中,并返回添加的元素个数 |
| remainingCapacity() | 计算队列中还可以存放的元素个数 |
| toArray() | 以objetc数组的形式获取队列中所有的元素 |
| toArray(T[] a) | 以给定类型数组的方式获取队列中所有的元素 |
| clear() | 清空队列,危险的操作 |
阻塞队列的实现原理
阻塞队列的实现依靠通知模式实现:当生产者向满了的队列中添加元素时,会阻塞住生产者,
直到消费者消费了一个队列中的元素后会通知消费者队列可用,此时再由生产者向队列中添加元素。反之亦然。
阻塞队列的阻塞唤醒依靠Condition——条件队列来实现。
以ArrayBlockingQueue为例说明:
ArrayBlockingQueue的定义:
public class ArrayBlockingQueue<E> extends AbstractQueue<E>
implements BlockingQueue<E>, java.io.Serializable {
/** The queued items */
//以数组的结构存储队列的元素,采用的是循环数组
final Object[] items;
/** items index for next take, poll, peek or remove */
//队列的队头索引
int takeIndex;
/** items index for next put, offer, or add */
//队列的队尾索引
int putIndex;
/** Number of elements in the queue */
//队列中元素的个数
int count;
/** Main lock guarding all access */
//对于ArrayBlockingQueue所有的操作都需要加锁,
final ReentrantLock lock;
/** Condition for waiting takes */
//条件队列,当队列为空时阻塞消费者并在生产者生产后唤醒消费者
private final Condition notEmpty;
/** Condition for waiting puts */
//条件队列,当队列满时阻塞生产者,并在消费者消费队列后唤醒生产者
private final Condition notFull;
}
根据类的定义字段可以看到,有两个Condition条件队列,猜测以下过程
- 当队列为空,消费者试图消费时应该调用
notEmpty.await()方法阻塞,并在生产者生产后调用notEmpty.single()方法 - 当队列已满,生产者试图放入元素应调用
notFull.await()方法阻塞,并在消费者消费队列后调用notFull.single()方法
向队列中添加元素put()方法的添加过程。
/**
* 向队列中添加元素
* 当队列已满时需要阻塞当前线程
* 放入元素后唤醒因队列为空阻塞的消费者
*/
public void put(E e) throws InterruptedException {
checkNotNull(e);
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
//当队列已满时需要notFull.await()阻塞当前线程
//offer(e,time,unit)方法就是阻塞的时候加了超时设定
while (count == items.length)
notFull.await();
//放入元素的过程
enqueue(e);
} finally {
lock.unlock();
}
}
/**enqueue实际添加元素的方法*/
private void enqueue(E x) {
// assert lock.getHoldCount() == 1;
// assert items[putIndex] == null;
final Object[] items = this.items;
items[putIndex] = x;
if (++putIndex == items.length)
putIndex = 0;
count++;
//如果条件队列中存在等待的线程
//唤醒
notEmpty.signal();
}
从队列中获取元素take()方法的获取过程。
/**
* 从队列中获取元素
* 当队列已空时阻塞当前线程
* 从队列中消费元素后唤醒等待的生产线程
*/
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
//队列为空需要阻塞当前线程
while (count == 0)
notEmpty.await();
//获取元素的过程
return dequeue();
} finally {
lock.unlock();
}
}
/**dequeue实际消费元素的方法*/
private E dequeue() {
// assert lock.getHoldCount() == 1;
// assert items[takeIndex] != null;
final Object[] items = this.items;
@SuppressWarnings("unchecked")
E x = (E) items[takeIndex];
items[takeIndex] = null;
if (++takeIndex == items.length)
takeIndex = 0;
count--;
if (itrs != null)
itrs.elementDequeued();
//消费元素后从唤醒阻塞的生产者线程
notFull.signal();
return x;
}
总结
阻塞队列提供了不同于普通队列的增加、删除元素的方法,核心在与队列满时阻塞生产者和队列空时阻塞消费者。
这一阻塞过程依靠与锁绑定的Condition对象实现。Condition接口的实现在AQS中实现,具体的实现类是
ConditionObject
阻塞队列一——java中的阻塞队列的更多相关文章
- 聊聊并发(七)——Java中的阻塞队列
3. 阻塞队列的实现原理 聊聊并发(七)--Java中的阻塞队列 作者 方腾飞 发布于 2013年12月18日 | ArchSummit全球架构师峰会(北京站)2016年12月02-03日举办,了解更 ...
- Java中的阻塞队列(BlockingQueue)
1. 什么是阻塞队列 阻塞队列(BlockingQueue)是 Java 5 并发新特性中的内容,阻塞队列的接口是 java.util.concurrent.BlockingQueue,它提供了两个附 ...
- Java中的阻塞队列-ArrayBlockingQueue(一)
最近在看一些java基础的东西,看到了队列这章,打算对复习的一些知识点做一个笔记,也算是对自己思路的一个整理,本章先聊聊java中的阻塞队列 参考文章: http://ifeve.com/java-b ...
- 多线程编程学习六(Java 中的阻塞队列).
介绍 阻塞队列(BlockingQueue)是指当队列满时,队列会阻塞插入元素的线程,直到队列不满:当队列空时,队列会阻塞获得元素的线程,直到队列变非空.阻塞队列就是生产者用来存放元素.消费者用来获取 ...
- JUC之Java中的阻塞队列及其实现原理
在文章线程池实现原理 - 池塘里洗澡的鸭子 - 博客园 (cnblogs.com)中介绍了线程池的组成部分,其中一个组成部分就是阻塞队列.那么JAVA中的阻塞队列如何实现的呢? 阻塞队列,关键字是阻塞 ...
- AQS源码深入分析之条件队列-你知道Java中的阻塞队列是如何实现的吗?
本文基于JDK-8u261源码分析 1 简介 因为CLH队列中的线程,什么线程获取到锁,什么线程进入队列排队,什么线程释放锁,这些都是不受我们控制的.所以条件队列的出现为我们提供了主动式地.只有满足指 ...
- Java中的阻塞和非阻塞IO包各自的优劣思考(经典)
Java中的阻塞和非阻塞IO包各自的优劣思考 NIO 设计背后的基石:反应器模式,用于事件多路分离和分派的体系结构模式. 反应器(Reactor):用于事件多路分离和分派的体系结构模式 通常的,对一个 ...
- Java中的阻塞队列
1. 什么是阻塞队列? 阻塞队列(BlockingQueue)是一个支持两个附加操作的队列.这两个附加的操作是:在队列为空时,获取元素的线程会等待队列变为非空.当队列满时,存储元素的线程会等待队列可用 ...
- java 中的阻塞队列
1.什么是阻塞队列: 支持阻塞的插入方法,意思是当队列满时,队列会阻塞插入元素的线程,知道队列不满. 支持阻塞的移除方法:意思是在队列为空时,获取元素的线程会等待队列变为非空. 插入和移除操作的4种处 ...
随机推荐
- clickhouse入门到实战及面试
第一章. clickhouse入门 一.ClickHouse介绍 ClickHouse(开源)是一个面向列的数据库管理系统(DBMS),用于在线分析处理查询(OLAP). 关键词:开源.面向列.联机分 ...
- 使用interllij IDEA 写第一个Java程序
安装interllij IDEA interllij IDEA简称IDEA,是最好用的Java集成开发环境.你只需要安装一个IDEA,就可以立马开始学习Java,不用再费心去配置Java环境. IDE ...
- poj1780欧拉回路
转载 #include<cstdio> #include<cstring> ; bool vis[N]; char ans[N]; int main() { int n; wh ...
- 【python爬虫】解决歌荒,下歌利器
python下载图片,mp3,想必很多人都早已耳闻,今天给大家来点不一样的, 让你下载高逼格高品质,带进度条,实时显示下载速度 详见源码:https://www.kesci.com/home/proj ...
- 利用SQL语句替换织梦DedeCms数据库内容
找到 然后把命令放在 执行即可,慎重执行 替换文章标题 UPDATE `dede_archives` SET `title`=replace(title,'我我我','你你你') 替换文章正文 UPD ...
- MySQL高可用篇之MHA集群
1 准备工作 1.1 修改主机名 vim /etc/hosts # 添加对应主机 192.168.28.128 mha1 192.168.28.131 mha2 192.168.28.132 mha3 ...
- MySQL8多实例安装与mycat连接,最详细版本。
[版权所有,转载请注明出处!违者必究!] 最近在搞mycat去实现主从库读写分离,所以博主就在自己的windows机器上进行了环境的搭建,在搭建MySQL多实例的时候还算顺利,就是mysql8和myc ...
- 在 Linux 系统中如何管理 systemd 服务
在上一篇文章<Linux的运行等级与目标>中,我介绍过 Linux 用 systemd 来取代 init 作为系统的初始化进程.尽管这一改变引来了很多争议,但大多数发行版,包括 RedHa ...
- 《HelloGitHub》第 50 期
兴趣是最好的老师,HelloGitHub 就是帮你找到兴趣! 简介 分享 GitHub 上有趣.入门级的开源项目. 这是一个面向编程新手.热爱编程.对开源社区感兴趣 人群的月刊,月刊的内容包括:各种编 ...
- windows+vs2017+C语言 引入mysql.h对MYSQL数据库的操作
mysql.h文件用作VS开发用,用来连接数据库.没有mysql.h文件,就无法调用mysql的东西 也无法用C语言对MYSQL操作. 一般安装了mysql之后,这个文件就在mysql目录的inclu ...