目录

  • 阻塞队列简介:介绍阻塞队列的特性与应用场景
  • 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中的阻塞队列的更多相关文章

  1. 聊聊并发(七)——Java中的阻塞队列

    3. 阻塞队列的实现原理 聊聊并发(七)--Java中的阻塞队列 作者 方腾飞 发布于 2013年12月18日 | ArchSummit全球架构师峰会(北京站)2016年12月02-03日举办,了解更 ...

  2. Java中的阻塞队列(BlockingQueue)

    1. 什么是阻塞队列 阻塞队列(BlockingQueue)是 Java 5 并发新特性中的内容,阻塞队列的接口是 java.util.concurrent.BlockingQueue,它提供了两个附 ...

  3. Java中的阻塞队列-ArrayBlockingQueue(一)

    最近在看一些java基础的东西,看到了队列这章,打算对复习的一些知识点做一个笔记,也算是对自己思路的一个整理,本章先聊聊java中的阻塞队列 参考文章: http://ifeve.com/java-b ...

  4. 多线程编程学习六(Java 中的阻塞队列).

    介绍 阻塞队列(BlockingQueue)是指当队列满时,队列会阻塞插入元素的线程,直到队列不满:当队列空时,队列会阻塞获得元素的线程,直到队列变非空.阻塞队列就是生产者用来存放元素.消费者用来获取 ...

  5. JUC之Java中的阻塞队列及其实现原理

    在文章线程池实现原理 - 池塘里洗澡的鸭子 - 博客园 (cnblogs.com)中介绍了线程池的组成部分,其中一个组成部分就是阻塞队列.那么JAVA中的阻塞队列如何实现的呢? 阻塞队列,关键字是阻塞 ...

  6. AQS源码深入分析之条件队列-你知道Java中的阻塞队列是如何实现的吗?

    本文基于JDK-8u261源码分析 1 简介 因为CLH队列中的线程,什么线程获取到锁,什么线程进入队列排队,什么线程释放锁,这些都是不受我们控制的.所以条件队列的出现为我们提供了主动式地.只有满足指 ...

  7. Java中的阻塞和非阻塞IO包各自的优劣思考(经典)

    Java中的阻塞和非阻塞IO包各自的优劣思考 NIO 设计背后的基石:反应器模式,用于事件多路分离和分派的体系结构模式. 反应器(Reactor):用于事件多路分离和分派的体系结构模式 通常的,对一个 ...

  8. Java中的阻塞队列

    1. 什么是阻塞队列? 阻塞队列(BlockingQueue)是一个支持两个附加操作的队列.这两个附加的操作是:在队列为空时,获取元素的线程会等待队列变为非空.当队列满时,存储元素的线程会等待队列可用 ...

  9. java 中的阻塞队列

    1.什么是阻塞队列: 支持阻塞的插入方法,意思是当队列满时,队列会阻塞插入元素的线程,知道队列不满. 支持阻塞的移除方法:意思是在队列为空时,获取元素的线程会等待队列变为非空. 插入和移除操作的4种处 ...

随机推荐

  1. Spring IoC createBean 方法详解

    前言 本篇文章主要分析 Spring IoC 的 createBean() 方法的流程,以及 bean 的生命周期. 下面是一个大致的流程图: 正文 AbstractAutowireCapableBe ...

  2. 苏浪浪 201771010120《面向对象程序设计(java)》第八周学习总结

    1.实验目的与要求 (1) 掌握接口定义方法: (2) 掌握实现接口类的定义要求: (3) 掌握实现了接口类的使用要求: (4) 掌握程序回调设计模式: (5) 掌握Comparator接口用法: ( ...

  3. POJ3225

    题目链接:https://vjudge.net/problem/POJ-3225 解题思路:这道题要是不看题解以本渣新现在的实力确实是做不出来. 以区间为基础建立线段树. 当X=‘U', 将区间T内的 ...

  4. 【面试考】【入门】决策树算法ID3,C4.5和CART

    关于决策树的purity的计算方法可以参考: 决策树purity/基尼系数/信息增益 Decision Trees 如果有不懂得可以私信我,我给你讲. ID3 用下面的例子来理解这个算法: 下图为我们 ...

  5. Docker搭建VS Code Server ,设置访问密码随时随地写代码

    今天在N1盒子上安装了 VS Code Server,简单的记录一下. 安装docker Docker一键安装脚本 $ sudo wget -qO- https://get.docker.com/ | ...

  6. SPL常用迭代器

    ArrayIterator 熟悉使用seek()跳过元素 熟悉使用asort,ksort排序 <?php $fruits = array( 'apple'=>'apple value', ...

  7. Nginx 实现 HTTPS(基于 Let's Encrypt 的免费证书)

    SSL / TLS加密会为您的用户带来更高的搜索排名和更好的安全性. Let’s Encrypt 是一个认证机构(CA).它可以提供免费证书,并且已经被大多数浏览器所信任.另外,通过工具 Certbo ...

  8. JVM调优总结(六)-新一代的垃圾回收算法

    垃圾回收的瓶颈 传统分代垃圾回收方式,已经在一定程度上把垃圾回收给应用带来的负担降到了最小,把应用的吞吐量推到了一个极限.但是他无法解决的一个问题,就是Full GC所带来的应用暂停.在一些对实时性要 ...

  9. Order by 优化

    写在前面 文章涉及到的 customer 表来源于案例库 sakila,下载地址为 http://downloads.mysql.com/docs/sakila-db.zip MySQL 排序方式 通 ...

  10. [JavaWeb基础] 003.JAVA访问Mysql数据库

    上面两篇讲解了简单的JSP + Servlet的搭建和请求,那么后面我们肯定要用到数据交互,也就是操纵数据库的数据,包括对数字的增加,删除,修改,查询.我们就用简单的MySql来做例子 我们需要引入驱 ...