深入了解ArrayBlockingQueue 阻塞队列

news2025/1/15 16:35:44

1. 前言

开始正式了解阻塞队列之前,我们需要了解什么是队列队列有什么作用。其实队列的作用就是解耦,更加确切的说应该是生产者以及消费者 之间的解耦

今天就让我们来看下ArrayBlockingQueue 的实现。虽然通过名称就可以看到,无非是通过数组进行实现但是我们还是要剥离下Java底层代码是如何实现的。

2. 简单示例

package com.lihh.queue;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.TimeUnit;

public class T01_Queue_Test01 {
    public static void main(String[] args) throws InterruptedException {
//        addTest();
//        offerTest();
//        offerTest01();
        putTest();
    }

    public static void putTest() throws InterruptedException {
        ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue<>(2);

        arrayBlockingQueue.put("1");
        arrayBlockingQueue.put("2");

        new Thread(() -> {
            try {
                Thread.sleep(2000);
                arrayBlockingQueue.remove();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }).start();

        // 一直处于阻塞状态
        arrayBlockingQueue.put("3");
        System.out.println(arrayBlockingQueue);
    }

    public static void offerTest01() throws InterruptedException {
        ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue<>(2);
        System.out.println(arrayBlockingQueue.offer("1"));
        System.out.println(arrayBlockingQueue.offer("2"));

        new Thread(() -> {
            try {
                Thread.sleep(1000);
                System.out.println(arrayBlockingQueue.remove());
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }).start();

        // 此处表示指定时间范围内 时间是阻塞状态
        System.out.println(arrayBlockingQueue.offer("3", 3, TimeUnit.SECONDS));
    }

    public static void offerTest() {
        ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue<>(2);
        System.out.println(arrayBlockingQueue.offer("1"));
        System.out.println(arrayBlockingQueue.offer("2"));

        // 如果队列的长度已满 ,无法添加新的元素,直接返回false
        System.out.println(arrayBlockingQueue.offer("3"));
    }

    public static void addTest() {
        ArrayBlockingQueue<String> strings = new ArrayBlockingQueue<>(2);
        strings.add("1");
        strings.add("2");
        // 如果超过队列的长度 直接报错
        strings.add("3");
    }
}

3. Java层面分析

3.1 constructor 实现

3.1.1 定义

在这里插入图片描述
其实通过构造函数重写就可以看到,第一个参数一定是一个int类型的值。 既然底层是基于数组进行实现的。所以数组必须限定一个长度,就是这个所谓的int类型所代表的值。

3.1.2 内容

// 数组阻塞队列的实现方法。 
// capacity 表示数组的长度
// fair 表示是公平锁 还是 非公平锁
public ArrayBlockingQueue(int capacity, boolean fair) {

    // 如果长度小于等于0的话 直接报异常错误
    if (capacity <= 0)
        throw new IllegalArgumentException();
    // 实例化一个对象数组
    this.items = new Object[capacity];
    // 实例化一个锁
    lock = new ReentrantLock(fair);
    notEmpty = lock.newCondition();
    notFull =  lock.newCondition();
}

3.2 生产者实现方法

3.2.1 add 实现方法

通过内部APIoffer来添加元素。添加成功后返回true。反之就会报错。(如果队列中元素满了也会添加失败,也会报错误)

public boolean add(E e) {
    // 内部直接调用offer方法,如果元素添加成功后返回true。 反之直接就是抛出异常
    if (offer(e))
        return true;
    else
        throw new IllegalStateException("Queue full");
}

3.2.2 offer 实现方法

此方法相对简单,就往队列中添加元素,如果添加成功就返回true,反之就返回false。

// 添加元素的方法
public boolean offer(E e) {
    // 判断是否为空值
    checkNotNull(e);
    // 获取锁示例
    final ReentrantLock lock = this.lock;
    // 枷锁
    lock.lock();
    try {
        // 如果添加的元素的个数 == 数组的长度了。 直接返回false
        if (count == items.length)
            return false;
        else {
            enqueue(e);
            return true;
        }
    } finally {
        // 解锁
        lock.unlock();
    }
}


private void enqueue(E x) {
    // 成员 => 局部
    final Object[] items = this.items;
    // 给指定位置 设置元素。 变量putIndex 控制添加元素移动的
    items[putIndex] = x;
    if (++putIndex == items.length)
        // 
        putIndex = 0;
    // 元素个数累加的
    count++;
    // 将Condition中 阻塞的线程唤醒。
    notEmpty.signal();
}

3.2.3 有参offer实现方法

offer(int)的用法保持一致。但是唯一不同的是:如果队列中元素满了后,线程会被阻塞一定的时间,如果时间到期后还不能添加到队列中,就直接返回false

// 添加元素 但是会有一定时间的阻塞
public boolean offer(E e, long timeout, TimeUnit unit)
    throws InterruptedException {

    // 判断是否为空
    checkNotNull(e);
    // 统一转换时间
    long nanos = unit.toNanos(timeout);
    // 获取锁
    final ReentrantLock lock = this.lock;
    // 设置可 中断锁
    lock.lockInterruptibly();
    try {
        // 如果队列中的元素已满的话 while等待中
        while (count == items.length) {
            // 如果等待时间到了 直接返回false
            if (nanos <= 0)
                return false;
			
			// 挂起线程,同时释放资源
			// awaitNanos会挂起线程,并且返回剩余的阻塞时间
			// 恢复执行时,需要重新获取锁资源
            nanos = notFull.awaitNanos(nanos);
        }
        enqueue(e);
        return true;
    } finally {
        lock.unlock();
    }
}

3.2.3 put 实现方法

也是往队列中添加元素的方法,如果队列中元素满了,会一直处于阻塞状态,直到可以添加到底队列为止。

// 添加元素
public void put(E e) throws InterruptedException {
    // 判断是否为空
    checkNotNull(e);
    // 获取锁实例
    final ReentrantLock lock = this.lock;
    // 加 可打断锁
    lock.lockInterruptibly();
    try {
        // await方法一直阻塞,直到被唤醒或者中断标记位
        while (count == items.length)
            notFull.await();
        enqueue(e);
    } finally {
        // 删除锁
        lock.unlock();
    }
}

3.3 消费者实现方法

3.3.1 remove 实现方法

依据队列的特性进行元素删除,当队列中没有元素了,直接报异常

// 删除元素的方法
public E remove() {
    // 内部直接使用poll方法来实现
    E x = poll();
    // 如果结果不为null的话 直接返回
    if (x != null)
        return x;
    else
        // 反之就是报异常
        throw new NoSuchElementException();
}

3.3.2 poll 实现方法

删除元素的API,如果队列中没有元素了直接返回null

// 删除元素的方法
public E poll() {
    // 获取锁
    final ReentrantLock lock = this.lock;
    // 进行加锁
    lock.lock();
    try {
        // 如果队列中没有数据了 直接返回null。 反之就是删除数据
        return (count == 0) ? null : dequeue();
    } finally {
        // 解锁
        lock.unlock();
    }
}


private E dequeue() {
    // 保存元素的队列
    final Object[] items = this.items;
    @SuppressWarnings("unchecked")
    // 获取将要剔除的元素
    E x = (E) items[takeIndex];
    // 将剔除的元素重置为null
    items[takeIndex] = null;
    // 如果已经删除的是队列的最后一个元素了 将下标设置为第一个元素
    if (++takeIndex == items.length)
        takeIndex = 0;
    // 元素递减
    count--;
    if (itrs != null)
        itrs.elementDequeued();
    // signal方法,会唤醒当前Condition中排队的一个Node。
    notFull.signal();
    return x;
}

3.3.3 有参poll实现方法

删除元素的API,当队列中没有数据的时候,可以阻塞等待指定时间,时间到期后队列中还没有数据的话,直接返回null

public E poll(long timeout, TimeUnit unit) throws InterruptedException {
    // 获取阻塞的时间
    long nanos = unit.toNanos(timeout);
    // 获取锁
    final ReentrantLock lock = this.lock;
    // 加锁
    lock.lockInterruptibly();
    try {
        // while循环 直到存在数据跳出循环
        while (count == 0) {
            // 如果时间到期了 直接跳过
            if (nanos <= 0)
                return null;
            // 没数据,挂起消费者线程
            nanos = notEmpty.awaitNanos(nanos);
        }
        return dequeue();
    } finally {
        lock.unlock();
    }
}

3.3.4 take 实现方法

删除元素的API,如果队列中没有元素了会一直挂起线程等待。

public E take() throws InterruptedException {
    // 获取锁
    final ReentrantLock lock = this.lock;
    // 设置加锁
    lock.lockInterruptibly();
    try {
        // 线程挂起,直到队列中出现元素
        while (count == 0)
            notEmpty.await();
        return dequeue();
    } finally {
        lock.unlock();
    }
}

4. 结论

大体的分析就到这里了,其实代码还是比较简单的。如果大家有什么疑问,及时在评论区留言哦。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/156168.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Theory for the information-based decomposition of stock price

文章目录MotivationThe potential of Brogaard DecompositionIntuitions for Brogaard decompositionTechnique details in Brogaard decompositionDefine the VAR systemIdentify the VAR systemVariance decompositionSummaryMain ReferencesMotivation Brogaard et al. (20…

1000字带您了解网络设备的接口分类和接口编号规则

通过本文&#xff0c;您可以了解到设备的接口分类和接口编号规则。 文章目录一、接口分类1.1 物理接口1.1.1 管理接口1.1.1 业务接口LAN侧接口WAN侧接口1.2 逻辑接口二、接口编号规则2.1 物理接口编号规则三、总结一、接口分类 接口是设备与网络中的其它设备交换数据并相互作用…

3.3 行列式的几何意义

文章目录二维面积三维体积多维体积行列式是线性代数一个非常重要的内容&#xff0c;也是非常难的领域.行列式在欧几里得空间里还有特殊的几何意义。二维面积 &esmp; 两个向量围成的平行四边形的面积就是这两个向量组成的矩阵的行列式的绝对值。以两个向量(3.−2)T(3.-2)^…

结构体 · 内存对齐

欢迎来到 Claffic 的博客 &#x1f49e;&#x1f49e;&#x1f49e; 前言&#xff1a; 在初识C语言中简单介绍了结构体&#xff0c;结构体可以理解为不同类型数据的集合体&#xff0c;但是你想过结构体的大小是如何计算的吗&#xff1f;看完这篇博客&#xff0c;你就能给自己答…

Linux 计算机网络 route 路由表、多网段与 bond 的故事

Linux 计算机网络 route 路由表、多网段与 bond 的故事 序 在之前的章节中&#xff0c;介绍了计算机网络的发展以及各种解析&#xff0c;在之中我们提到了每个主机设备都会维护一张自己的路由表&#xff0c;通过路由表来确定在不同网络之间&#xff0c;怎么将数据规划传输到各…

1988-2020年31省基尼系数数据

1、时间&#xff1a;1988-2020年 2、范围&#xff1a;31省 3、指标&#xff1a;包括省基尼系数年度数据&#xff0c;省城市和农村基尼系数年度 4、来源及计算方法说明附在文件内 5、指标说明&#xff1a; 基尼系数&#xff08;英文&#xff1a;Gini index、Gini Coefficie…

LeetCode 94. 二叉树的中序遍历

&#x1f308;&#x1f308;&#x1f604;&#x1f604; 欢迎来到茶色岛独家岛屿&#xff0c;本期将为大家揭晓LeetCode 94. 二叉树的中序遍历&#xff0c;做好准备了么&#xff0c;那么开始吧。 &#x1f332;&#x1f332;&#x1f434;&#x1f434; 一、题目名称 LeetC…

Mybatis获取参数

Mybatis获取参数 配置模板 mybatis获取参数值的两种方式 1、&{}&#xff1a; 字符串拼接 2、#{}&#xff1a; 占位符赋值 MyBatis获取参数值的各种情况&#xff1a; MyBatis获取参数值的各种情况&#xff1a; 1、mapper接口方法的参数为单个的字面量类型 可以通过&#xf…

双系统下 linux挂载window磁盘

如果想让linux访问window分区磁盘&#xff0c;呈只读状态&#xff0c;解决办法是bios取消window快速开机。永久挂载windows磁盘 https://blog.csdn.net/yuehenmiss/article/details/124737456 # 创建挂载目录 sudo mkdir /window # 挂载分区 sudo mount /dev/sda1 /window # 查…

产品经理必懂知识之计算机基础知识

作为产品经理&#xff0c;非常有必要了解一下计算机的发展历史&#xff0c;今天带大家一起&#xff0c;大概地了解一下计算机的基础知识&#xff0c;希望能够帮助到大家&#xff0c;框架如下&#xff1a; 一、计算机发展史 1.1计算机的诞生 1946年第一台电子计算机问世美国宾…

YOLOv8训练自己的数据集(超详细)

一、准备深度学习环境 本人的笔记本电脑系统是&#xff1a;Windows10 YOLO系列最新版本的YOLOv8已经发布了&#xff0c;详细介绍可以参考我前面写的博客&#xff0c;目前ultralytics已经发布了部分代码以及说明&#xff0c;可以在github上下载YOLOv8代码&#xff0c;代码文件夹…

一种车辆纵向控制切换算法设计思路

传统及主流的纵向控制切换算法&#xff1a; 例如《某避障控制策略研究》硕士论文&#xff1a; 在CarSim中设定节气门开度及制动踏板力为0&#xff0c;测得不同车速工况下车辆自然滑行的减速度。为了避免在控制过程中车辆驱动与制动切换的过于频繁&#xff0c;在其两侧设置了宽…

VUE_vue-cli 卸载不掉的问题解决

nodejs版本最好在v17以下&#xff0c;推荐使用v16.19.0 问题 由于项目需要旧版的 vue-cli &#xff0c;所以需要事先卸载新版本&#xff1b; 运行命令全局卸载&#xff1a; yarn global remove vue/cli// 查看当前版本确定是否卸载 vue --version结果还是旧版本&#xff0c;…

使用ResNet34实现CIFAR100数据集的训练

如果对你有用的话&#xff0c;希望能够点赞支持一下&#xff0c;这样我就能有更多的动力更新更多的学习笔记了。&#x1f604;&#x1f604; 使用ResNet进行CIFAR-10数据集进行测试&#xff0c;这里使用的是将CIFAR-10数据集的分辨率扩大到32X32&#xff0c;因为算力相关的…

5.8.1、TCP的连接建立

TCP 是面向连接的协议&#xff0c;它基于运输连接来传送 TCP 报文段。 TCP 运输连接的建立和释放是每一次面向连接的通信中必不可少的过程。 TCP 运输连接有以下三个阶段 建立 TCP 连接&#xff1a;通过 “三报文握手” 建立 TCP 连接数据传送&#xff1a;也就是基于已建立的…

【PostgreSQL】手把手教学PostgreSQL

目录 1、PostgreSQL介绍 2、在ubuntu上通过命令安装 3、进入postgres用户 4、查看所有数据库 5、创建数据库 6、删除数据库 7、查看版本号&#xff08;注意&#xff1a;在sudo su - postgres下&#xff09; 8、远程连接 1、PostgreSQL介绍 官网&#xff1a;PostgreSQL: T…

SiC碳化硅功率器件测试哪些方面?碳化硅功率器件测试系统NSAT-2000

SiC碳化硅功率半导体器件具有耐压高、热稳定好、开关损耗低、功率密度高等特点&#xff0c;被广泛应用在电动汽车、风能发电、光伏发电等新能源领域。 近年来&#xff0c;全球半导体功率器件的制造环节以较快速度向我国转移。目前,我国已经成为全球最重要的半导体功率器件封测基…

wndows平台VS2019+OpenCV+cmake简单应用

wndows平台VS2019OpenCVcmake简单应用1.下载并解压文件2.结合人脸检测demo在vs中进行配置2.1 人脸检测代码2.2 在VS项目—属性中配置2.2.1 配置包含目录2.2.2 配置库目录2.2.3 配置链接器附加依赖项2.3 通过cmake进行配置与编译2.3.1 添加CMakeLists.txt文件2.3.2 cmake命令行执…

普中学习板准备工作

目录 1.1 ch341驱动安装 1. 目标板上的usb-串口模块插上 2. 按下目标板上的上电按钮 3. 打开ch341驱动程序&#xff0c;点击安装&#xff0c;等待结果 1.2 使用自动下载软件 1. 使用普中的自动下载软件 2. 串口号处选择安装好的驱动端口 3. 打开文件选择编译好的程序 …

2023 RealWorldCTF “Ferris proxy”逆向题分析(不算wp)

这题第二天才开始做&#xff0c;结果到比赛后4个小时才做出来&#xff0c;真是老了&#xff0c;不过也算有收获&#xff0c;对rust的程序更熟悉了~ client编译后的代码有41M&#xff0c;WTF 主函数入口 根据main函数找到两个入口 第二个函数很明显是主入口&#xff0c;不过…