详解java中的阻塞队列
阻塞队列简介
阻塞队列(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 阻塞队列的资料请关注猪先飞其它相关文章!
相关文章
- 这篇文章主要介绍了如何利用java语言实现经典《复杂迷宫》游戏,文中采用了swing技术进行了界面化处理,感兴趣的小伙伴可以动手试一试...2022-02-01
java 运行报错has been compiled by a more recent version of the Java Runtime
java 运行报错has been compiled by a more recent version of the Java Runtime (class file version 54.0)...2021-04-01- 这篇文章主要介绍了在java中获取List集合中最大的日期时间操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2020-08-15
- 这篇文章主要介绍了教你怎么用Java获取国家法定节假日,文中有非常详细的代码示例,对正在学习java的小伙伴们有非常好的帮助,需要的朋友可以参考下...2021-04-23
- 这篇文章主要介绍了Java如何发起http请求的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2021-03-31
- 说起C#和Java这两门语言(语法,数据类型 等),个人以为,大概有90%以上的相似,甚至可以认为几乎一样。但是在工作中,我也发现了一些细微的差别...2020-06-25
- 这篇文章主要介绍了解决Java处理HTTP请求超时的问题,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2021-03-29
- 队列的特性很简答,就是先进先出,一般利用数组来实现,本文就介绍了C#队列的简单使用,文中根据实例编码详细介绍的十分详尽,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...2022-03-17
- 这篇文章主要介绍了java 判断两个时间段是否重叠的案例,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2020-08-15
java 画pdf用itext调整表格宽度、自定义各个列宽的方法
这篇文章主要介绍了java 画pdf用itext调整表格宽度、自定义各个列宽的方法,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2021-01-31- 这篇文章主要介绍了超简洁java实现双色球若干注随机号码生成(实例代码),本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下...2021-04-02
- 这篇文章主要介绍了SpringBoot集成Redis实现消息队列的方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2021-02-10
- 这篇文章主要介绍了Java生成随机姓名、性别和年龄的实现示例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-10-01
- 这篇文章主要介绍了java正则表达式判断前端参数修改表中另一个字段的值,需要的朋友可以参考下...2021-05-07
Java使用ScriptEngine动态执行代码(附Java几种动态执行代码比较)
这篇文章主要介绍了Java使用ScriptEngine动态执行代码,并且分享Java几种动态执行代码比较,需要的朋友可以参考下...2021-04-15- 这篇文章主要介绍了Java开发实现人机猜拳游戏,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...2020-08-03
- 这篇文章主要介绍了Java List集合返回值去掉中括号('[ ]')的操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2020-08-29
Java 8 Stream 的终极技巧——Collectors 功能与操作方法详解
这篇文章主要介绍了Java 8 Stream Collectors 功能与操作方法,结合实例形式详细分析了Java 8 Stream Collectors 功能、操作方法及相关注意事项,需要的朋友可以参考下...2020-05-20Java中lombok的@Builder注解的解析与简单使用详解
这篇文章主要介绍了Java中lombok的@Builder注解的解析与简单使用,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下...2021-01-06- 下面小编就为大家带来一篇java中String类型变量的赋值问题介绍。小编觉得挺不错的。现在分享给大家,给大家一个参考。...2016-03-28