jdk11源码--ArrayBlockingQueue源码分析

简介: jdk11 ArrayBlockingQueue源码分析

概述

上一篇文章jdk11源码--ReentrantLock之Condition源码分析中分析了ReentrantLock和Condition的源码,那么接下来看一下Condition在JDK中的具体应用。

ArrayBlockingQueue底层就是使用Condition来实现的。

BlockingQueue

BlockingQueue 阻塞队列,该类是一个接口,平时我们熟知的ArrayBlockingQueue,LinkedBlockingQueue等都是该接口的实现。
BlockingQueue 之所以说是阻塞的,是因为他可以在队列为空的时候,获取元素的线程会阻塞,直到有新的元素添加进来。当队列满时,添加元素的线程会阻塞,直到有线程从队列中取走了元素。这也是注明的==生产者消费者==问题。
当有面试官问你==生产者消费者==问题时,直接将ArrayBlockingQueue源码分析讲一下也是可以的。

看一下BlockingQueue的类图,BlockingQueue是继承自Queue,而queue继承自collection。
在这里插入图片描述

BlockingQueue 对插入、删除、获取原色的操作提供了四种不同的方法用于不同的场景中使用,这些方法总结在下表中:

抛出异常Throws exception Special value ==Blocks== Times out
插入数据Insert add(e) //队列满时抛异常 offer(e) //队列满返回false,不阻塞 ==put(e)== //队列满时阻塞,直到队列未满时再插入 offer(e, time, unit) //指定直接内可以插入返回true,指定时间内不能插入,返回false
获取数据Remove remove()//队列为空,抛异常 poll()//队列为空返回null,不阻塞 ==take()== //当队列为空时会阻塞,一直等到队列不为空时再返回队首值 poll(time, unit) //在指定时间内,队列都是空,则返回null,否则返回对首的值
Examine element() peek()

本文我们重点关注 put和take方法,因为这两个方法时阻塞的。

ArrayBlockingQueue

ArrayBlockingQueue是BlockingQueue的一个实现。他是FIFO先进先出队列。
ArrayBlockingQueue类图:
在这里插入图片描述
重要属性:

/** 队列集合,数组存储!! */
final Object[] items;
/** take, poll, peek or remove 方法获取元素的下标位置 */
int takeIndex;
/** put, offer, or add 方法添加元素的下标位置 */
int putIndex;

/** 队列中的元素数量 */
int count;

//并发控制
final ReentrantLock lock;
private final Condition notEmpty;
private final Condition notFull;

items是使用数组存储的,这也是ArrayBlockingQueue名称的由来。

并发控制使用经典的双Condition 算法,上面定义了两个Condition ,一个notEmpty,一个notFull。下面来逐行源码具体分析一下。

ArrayBlockingQueue构造方法

//capacity: 数组初始容量
public ArrayBlockingQueue(int capacity) {
    this(capacity, false);
}
//fair:是否是公平锁
public ArrayBlockingQueue(int capacity, boolean fair) {
    if (capacity <= 0)
        throw new IllegalArgumentException();
    this.items = new Object[capacity];
    lock = new ReentrantLock(fair);
    notEmpty = lock.newCondition();
    notFull =  lock.newCondition();
}

在构造函数中会指定初始容量以及锁的类型,默认是非公平锁。
数组items一旦确定下来,后续就不会再更改大小。

put

put方法添加元素到队尾,当队列满时阻塞。

public void put(E e) throws InterruptedException {
    Objects.requireNonNull(e);
    final ReentrantLock lock = this.lock;//获取锁lock
    lock.lockInterruptibly();//加锁,响应中断
    try {
        while (count == items.length)
            notFull.await();//如果items满了,那么notFull阻塞
        enqueue(e);//将元素添加到队列末尾
    } finally {
        lock.unlock();
    }
}

//将元素添加到队列末尾, ++putIndex,count++,
//该方法只允许在lock加锁后操作
private void enqueue(E e) {
    final Object[] items = this.items;
    items[putIndex] = e;
    if (++putIndex == items.length) putIndex = 0;
    count++;
    notEmpty.signal();//唤醒阻塞的获取元素的线程
}

整个过程还是比较简单的,首先加锁,注意这里的锁允许中断返回。然后,如果队列满了(count == items.length),那么就无法继续添加元素了,添加元素的线程就需要await等待(notFull.await();),该线程添加到notFull的condition队列上,直到被take方法唤醒(后面会讲)后,继续添加元素到队尾(enqueue(e);)。
enqueue方法中,添加元素到putIndex 的位置,然后对putIndex 加1操作,由于是数组,所以这里进行了越界处理,越界后从0开始继续计算。count元素的数量进行加1操作,同时唤醒notEmpty condition队列上阻塞的线程。
读者可以思考一下这里为什么没有进行这个校验:putIndex 在加一以后是否会与现有队列中已经存在的元素重合而覆盖掉现有元素?答案下一节揭晓。

take

take:从队首获取元素。

public E take() throws InterruptedException {
    final ReentrantLock lock = this.lock;
    lock.lockInterruptibly();//加锁,响应中断
    try {
        while (count == 0)
            notEmpty.await();//如果队列中空,那么当前线程需要添加到notEmpty的condition队列中阻塞,直到有新的元素添加进来
        return dequeue();
    } finally {
        lock.unlock();
    }
}

//从对首获取元素,
//该方法只允许在lock加锁后操作
private E dequeue() {
    final Object[] items = this.items;
    @SuppressWarnings("unchecked")
    E e = (E) items[takeIndex];
    items[takeIndex] = null;
    if (++takeIndex == items.length) takeIndex = 0;//设置takeIndex 下一次应该获取的位置
    count--;//队列总数量-1
    if (itrs != null)
        itrs.elementDequeued();//迭代器相关
    notFull.signal();//唤醒notFull condition队列上的线程
    return e;
}

take过程也很简单,先看一下队列是否为空,如果为空,则无法获取元素,将当前线程添加到notEmpty的condition队列。否则从takeIndex 的位置读取元素并且设置takeIndex ,count 的值。

上一节提到了这个问题:
putIndex 在加一以后是否会与现有队列中已经存在的元素重合而覆盖掉现有元素?
其实也很简单,enqueue和dequeue都是需要加锁以后才调用的,所以是线程安全的,count可以准确表示队列中的有效长度,takeIndex 和putIndex 也都没有并发问题,他们每次添加或者读取时都会判断count的值,来确认队列是否满或者空。如此一来,当然不会出现添加过多覆盖现有元素的情况。

总结

首先回顾一下上一篇jdk11源码--ReentrantLock之Condition源码分析中画的condition结构图吗,以及condition与ReentrantLock之间的关系。
然后再次基础上,画一下ArrayBlockingQueue中的condition关系图:
在这里插入图片描述
总体来讲,ArrayBlockingQueue包含一个ReentrantLock和两个condition:notEmpty和notFull。
put可take操作都需要加锁,都是线程安全的。
当队列满时,put操作需阻塞等待,当前线程添加到notFull的condition队列中;添加成功时,需唤醒notEmpty队列中的线程。
当队列空时,take操作需阻塞等待,当前线程添加到notEmpty的condition队列中;获取成功时,需唤醒notFull队列中的线程。

相关文章
|
5天前
|
数据采集 运维 前端开发
【Java】全套云HIS源码包含EMR、LIS (医院信息化建设)
系统技术特点:采用前后端分离架构,前端由Angular、JavaScript开发;后端使用Java语言开发。
23 5
|
2月前
|
Kubernetes jenkins 持续交付
从代码到k8s部署应有尽有系列-java源码之String详解
本文详细介绍了一个基于 `gitlab + jenkins + harbor + k8s` 的自动化部署环境搭建流程。其中,`gitlab` 用于代码托管和 CI,`jenkins` 负责 CD 发布,`harbor` 作为镜像仓库,而 `k8s` 则用于运行服务。文章具体介绍了每项工具的部署步骤,并提供了详细的配置信息和示例代码。此外,还特别指出中间件(如 MySQL、Redis 等)应部署在 K8s 之外,以确保服务稳定性和独立性。通过本文,读者可以学习如何在本地环境中搭建一套完整的自动化部署系统。
57 0
|
17天前
|
设计模式 Java 关系型数据库
【Java笔记+踩坑汇总】Java基础+JavaWeb+SSM+SpringBoot+SpringCloud+瑞吉外卖/谷粒商城/学成在线+设计模式+面试题汇总+性能调优/架构设计+源码解析
本文是“Java学习路线”专栏的导航文章,目标是为Java初学者和初中高级工程师提供一套完整的Java学习路线。
172 37
|
3天前
|
传感器 监控 数据可视化
【Java】智慧工地解决方案源码和所需关键技术
智慧工地解决方案是一种新的工程全生命周期管理理念。它通过使用各种传感器、数传终端等物联网手段获取工程施工过程信息,并上传到云平台,以保障数据安全。
22 7
|
12天前
|
机器学习/深度学习 数据采集 JavaScript
ADR智能监测系统源码,系统采用Java开发,基于SpringBoot框架,前端使用Vue,可自动预警药品不良反应
ADR药品不良反应监测系统是一款智能化工具,用于监测和分析药品不良反应。该系统通过收集和分析病历、处方及实验室数据,快速识别潜在不良反应,提升用药安全性。系统采用Java开发,基于SpringBoot框架,前端使用Vue,具备数据采集、清洗、分析等功能模块,并能生成监测报告辅助医务人员决策。通过集成多种数据源并运用机器学习算法,系统可自动预警药品不良反应,有效减少药害事故,保障公众健康。
ADR智能监测系统源码,系统采用Java开发,基于SpringBoot框架,前端使用Vue,可自动预警药品不良反应
|
2月前
|
算法 安全 Java
深入JDK源码:揭开ConcurrentHashMap底层结构的神秘面纱
【8月更文挑战第24天】`ConcurrentHashMap`是Java并发编程中不可或缺的线程安全哈希表实现。它通过精巧的锁机制和无锁算法显著提升了并发性能。本文首先介绍了早期版本中使用的“段”结构,每个段是一个带有独立锁的小型哈希表,能够减少线程间竞争并支持动态扩容以应对高并发场景。随后探讨了JDK 8的重大改进:取消段的概念,采用更细粒度的锁控制,并引入`Node`等内部类以及CAS操作,有效解决了哈希冲突并实现了高性能的并发访问。这些设计使得`ConcurrentHashMap`成为构建高效多线程应用的强大工具。
40 2
|
2月前
|
算法 安全 Java
深入解析Java多线程:源码级别的分析与实践
深入解析Java多线程:源码级别的分析与实践
|
2月前
|
存储 Java
【Java】Java学生成绩管理系统(源码+论文)【独一无二】
【Java】Java学生成绩管理系统(源码+论文)【独一无二】
|
2月前
|
SQL Java 数据库连接
【Java】Java Swing 图书管借阅管理系统(源码+论文)【独一无二】
【Java】Java Swing 图书管借阅管理系统(源码+论文)【独一无二】
|
2月前
|
存储 数据可视化 Java
【Java】Java swing 民宿管理系统 GUI(源码+可视化界面)【独一无二】
【Java】Java swing 民宿管理系统 GUI(源码+可视化界面)【独一无二】