详解java中的阻塞队列

 更新时间:2020年06月11日 15:57:31   作者:bmilk  
这篇文章主要介绍了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 阻塞队列的资料请关注脚本之家其它相关文章!

相关文章

  • JSON.parseObject和JSON.toJSONString实例详解

    JSON.parseObject和JSON.toJSONString实例详解

    这篇文章主要为大家详细介绍了JSON.parseObject和JSON.toJSONString实例,具有一定的参考价值,感兴趣的朋友可以参考一下
    2018-06-06
  • 详解Java中二叉树的基础概念(递归&迭代)

    详解Java中二叉树的基础概念(递归&迭代)

    二叉树(Binary tree)是树形结构的一个重要类型。本文将通过图片和示例代码详细为大家讲解一下Java中的二叉树的基础概念,需要的可以参考一下
    2022-03-03
  • Java面向对象选择题总结归纳

    Java面向对象选择题总结归纳

    今天小编就为大家分享一篇关于Java面向对象选择题总结归纳,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧
    2019-01-01
  • SpringBoot整合SpringTask实现定时任务的流程

    SpringBoot整合SpringTask实现定时任务的流程

    这篇文章主要介绍了SpringBoot整合SpringTask实现定时任务的流程,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-06-06
  • Springboot文件上传功能简单测试

    Springboot文件上传功能简单测试

    这篇文章主要介绍了Springboot文件上传功能简单测试,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-05-05
  • Java实现不同的类的属性之间相互赋值

    Java实现不同的类的属性之间相互赋值

    今天小编就为大家分享一篇关于Java实现不同的类的属性之间相互赋值,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧
    2019-03-03
  • idea中lombok启用的方法示例

    idea中lombok启用的方法示例

    这篇文章主要介绍了idea中lombok启用的方法示例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-07-07
  • 判断二叉树是否为完全二叉树的实例

    判断二叉树是否为完全二叉树的实例

    这篇文章主要介绍了判断二叉树是否为完全二叉树的实例的相关资料,需要的朋友可以参考下
    2017-05-05
  • 深层剖析java应用开发中MyBayis缓存

    深层剖析java应用开发中MyBayis缓存

    这篇文章主要为大家深层剖析java开发中MyBayis缓存,文中讲解了Mybatis缓存的分类以及使用的方式,有需要的朋友可以借鉴参考下,希望可以有所帮助
    2021-09-09
  • MyBatis映射文件中的动态SQL实例详解

    MyBatis映射文件中的动态SQL实例详解

    在本文中,我们深入探讨了动态SQL的各种标签,包括<if>、<choose>、<trim>、<foreach>等,通过实际的例子演示了它们的用法,感兴趣的朋友一起揭开动态SQL的神秘面纱,带你领略它的魅力
    2024-01-01

最新评论