AQS源码解析 4.Condition条件队列入门_手写BrokingQueue

news2024/11/20 2:35:57

AQS源码解析—Condition条件队列入门_手写BrokingQueue

Condition 条件队列介绍

  • AQS 中还有另一个非常重要的内部类 ConditionObject,它实现了 Condition 接口,主要用于实现条件锁。
  • ConditionObject 中也维护了一个队列,这个队列主要用于等待条件的成立,当条件成立时,其它线程将 signal 这个队列中的元素,将其移动到 AQS 的队列中,等待占有锁的线程释放锁后被唤醒。
  • Condition 典型的运用场景是在 BlockingQueue 中的实现,当队列为空时,获取元素的线程阻塞在 notEmpty 条件上,一旦队列中添加了一个元素,将通知 notEmpty 条件,将其队列中的元素移动到 AQS 队列中等待被唤醒。

管程模型(Monitor)

  • The Owner:The Owner 中只能有一个线程,就是持锁线程。
  • Entry Set:理解为竞争队列,抢锁失败后会进入这个队列中阻塞,当 The Owner 线程释放锁后,Entry Set 中所有的线程一起去竞争锁。
  • Wait Set:等待 (阻塞) 队列,如果线程在执行期间被 wait() 方式挂起,会释放锁 并进入 Wait Set 中等待,当满足条件时 等待队列中的线程则会被 notify() 随机唤醒,唤醒后该线程同样可以去竞争锁。

image-20221120144143764

这里只是简单分析一下管程模型(Monitor)=> wait() / notify(),后续关于 synchronized 的文章会详细分析;

而 Condition 条件队列就是在 Java 语言层面实现了这样类似的功能 => await() / signal()

两者区别:

  • Object => Monitor => Wait Set,一个 Object 只有一个 Monitor,同样也只有一个 Wait Set,notify() 只能随机唤醒 Wait Set 中的一个线程。
  • Condition => Lock.newCondition,ReentrantLock 的条件变量比 synchronized 强大之处在于支持多个条件变量,每个 Condition 对象都包含一个等待队列,可以创建 多个 Condition 对象,从而调用某个 conditionxx.signal(),唤醒该条件队列中的第一个线程,更细粒度的划分。

但不管是执行 wait() / notify() 还是 await() / signal(),前提都需要获得锁。

手写一个入门的 BrokingQueue

  • 通过手写阻塞队列的方式,来初步认识 AQS 中的 ConditionObject 对象,以及 Condition 的使用。
  • 当队列中元素满了就阻塞生产者线程并唤醒消费者线程,当队列为空就阻塞消费者线程并唤醒生产者线程。

BrokingQueue 接口

/**
 * @author lyc
 * @date 2022-10-26
 */
public interface BrokingQueue<T> {
    /**
     * 插入数据
     */
    void put(T element) throws InterruptedException;

    /**
     * 获取数据
     */
    T take() throws InterruptedException;
}

MiniArrayBrokingQueue 类

/**
 * @author lyc
 * @date 2022-10-26
 */
public class MiniArrayBrokingQueue implements BrokingQueue {

    /**
     * ReentrantLock锁:用于线程并发控制
     */
    private Lock lock = new ReentrantLock();

    /**
     * 条件队列1:notFull 存放生产者线程
     * 生产者线程生产数据时,会先检查当前queues是否已经满了,如果已满,则需要令当前生产者线程调用notFull.await()
     * 进入到notFull条件队列挂起,等待消费者线程消费数据时唤醒。
     */
    private Condition notFull = lock.newCondition();

    /**
     * 条件队列2:notEmpty 存放消费者线程
     * 消费者线程消费数据时,会先检查当前queues中是否有数据,如果没有数据,则需要令当前消费者线程调用notEmpty.await()
     * 进入到notEmpty条件队列挂起,等待生产者生产出数据时才能唤醒!
     */
    private Condition notEmpty = lock.newCondition();

    /**
     * 存储元素的数组队列
     */
    private Object[] queues;

    /**
     * 数组队列的长度
     */
    private int size;

    /**
     * count:当前队列中,可以被消费的数据量
     * putptr:记录生产者存放数据的下一次位置,每个生产者生产完一个数据后,会将 putptr++
     * takeptr:记录消费者消费数据的下一个位置,每个消费者消费完一个数据后,会将 takeptr++
     */
    private int count, putptr, takeptr;

    /**
     * 初始化构造方法
     * @param size:数组队列初始化大小
     */
    public MiniArrayBrokingQueue(int size) {
        this.size = size;
        this.queues = new Object[size];
    }

    /**
     * 插入数据 生产者
     * @param element
     */
    @Override
    public void put(Object element) throws InterruptedException {
        // 获取锁
        lock.lock();
        try {
            // 判断当前queues数组队列是否已满
            if (count == this.size) {
                // 如果已满,则进入到notFull条件队列挂起,等待消费者线程消费数据时才可以唤醒
                notFull.await();
            }

            // 执行到这里,说明数组队列queues未满,可以向队列中存放数据了
            this.queues[putptr] = element;
            putptr++;

            // 新存放数据后,判断是否达到了数组队列的最大值
            if (putptr == this.size) {
                // putptr恢复到0,为什么可以归零呢?
                // 因为:多线程执行条件下,总会有消费者线程在不断消费数据,即使当前生产者线程已经将putptr移动到size的位置,
                // 但是仍有可能有消费者线程将queues数组队列size位置之前的数据给消费掉了,那么此时queues其实并不算已经满了,
                // 那么将putptr恢复到0,就是为了让其不断去寻找queues数组中的空位,并用来存放element
                putptr = 0;
            }

            // 生产数据,自增count
            count++;

            // 当向队列中成功放入一个元素之后,需要做什么呢?
            // 需要给notEmpty一个唤醒信号,告诉消费者去消费
            notEmpty.signal();
        } finally {
            // 释放锁
            lock.unlock();
        }
    }

    /**
     * 获取数据 消费者
     * @return Object
     */
    @Override
    public Object take() throws InterruptedException {
        // 获取锁
        lock.lock();
        try {
            // 判断当前queues数组队列是否有数据可以被消费
            if (count == 0) {
                // 如果queues是空的,则进入到notEmpty队列进行挂起,等待生产者线程生产数据时才可以唤醒
                notEmpty.await();
            }

            // 执行到这里,说明队列中有数据可以被消费了
			// 消费数据后,为什么没有把桶位清空?=> 因为不需要做这步操作,指针操作来回交替,就会把之前有脏数据位置重写覆盖掉。
            Object element = this.queues[takeptr];
            takeptr++;

            // 新消费数据后,判断是否达到了数组队列的最大值
            if (takeptr == this.size) {
                // takeptr恢复到0,为什么可以归零呢?
                // 因为:多线程执行条件下,总会有生产者线程在不断生产数据,即使当前消费者线程已经将takeptr移动到size的位置,
                // 但是扔有可能有生产者线程向queues数组队列size之前的位置put数据,那么此时queues其实并不算空了,
                // 那么将takeptr恢复到0,就是为了让其不断去寻找queues数组中能被消费的数据
                takeptr = 0;
            }

            // 消费数据,自减count
            count--;

            // 当从队列中成功消费一个元素之后,需要做什么呢?
            // 需要给notFull一个唤醒信号,告诉生产者去生产
            notFull.signal();

            // 返回消费的element
            return element;
        } finally {
            // 释放锁
            lock.unlock();
        }
    }
}

测试

public class MiniArrayBrokingQueueTest {
    public static void main(String[] args) {
        BrokingQueue<Integer> queue = new MiniArrayBrokingQueue(10);
		// 生产者线程 生产数据
        Thread producer = new Thread(() -> {
            int i = 0;
            while (true) {
                i++;
                if (i == 10) {
                    i = 0;
                }

                try {
                    System.out.println("生产者线程生产数据:" + i);
                    queue.put(Integer.valueOf(i));
                    TimeUnit.MILLISECONDS.sleep(200);
                } catch (InterruptedException e) {
                }
            }
        });
        producer.start();
		// 消费者线程 消费数据
        Thread consumer = new Thread(() -> {
            while (true) {
                try {
                    Integer result = queue.take();
                    System.out.println("消费者线程消费数据:" + result);
                    TimeUnit.MILLISECONDS.sleep(200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        consumer.start();
    }
}

输出

生产者线程生产数据:1
消费者线程消费数据:1
生产者线程生产数据:2
消费者线程消费数据:2
生产者线程生产数据:3
消费者线程消费数据:3
生产者线程生产数据:4
消费者线程消费数据:4
生产者线程生产数据:5
消费者线程消费数据:5
生产者线程生产数据:6
消费者线程消费数据:6
生产者线程生产数据:7
消费者线程消费数据:7
生产者线程生产数据:8
消费者线程消费数据:8
生产者线程生产数据:9
消费者线程消费数据:9
生产者线程生产数据:0
消费者线程消费数据:0
生产者线程生产数据:1
消费者线程消费数据:1

Process finished with exit code -1

很完美的实现了 BrokingQueue 阻塞队列的 插入数据获取数据 的功能。




参考

  • 视频参考
    • b站_小刘讲源码付费课
  • 文章参考
    • 兴趣使然的草帽路飞_AQS源码探究_05 Conditon条件队列(手写一个入门的BrokingQueue)

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

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

相关文章

动态规划算法学习三:0-1背包问题

文章目录前言一、问题描述二、DP解决步骤1、最优子结构性质2、状态表示和递推方程3、算法设计与分析4、计算最优值5、算法实现6、缺点与思考前言 一、问题描述 二、DP解决步骤 1、最优子结构性质 2、状态表示和递推方程 子问题可由两个参数确定&#xff1a;待考虑装包的物品…

【C/调试实用技巧】—作为程序员应如何面对并尝试解决Bug?

小菜坤日常上传gitee代码&#xff1a;https://gitee.com/qi-dunyan 关于C语言的所有由浅入深的知识&#xff0c;都存放在专栏【c】 ❤❤❤ 个人简介&#xff1a;双一流非科班的一名小白&#xff0c;期待与各位大佬一起努力&#xff01; 推荐网站&#xff1a;cplusplus.com 目录…

Springboot集成ItextPdf

目录 一、概述 二、Itext API 1.PDF文档生成 3.常用对象 一、文档对象 二、操作对象 三、内容对象 四、表格对象 四、常用案例 一、水印 三、页眉页脚 四、合并多个PDF 五、表单PDF 六、模板PDF 一、html模板 二、使用工具构建PDF模板 7、HTML转PDF 8、删除页…

同花顺_知识_庄家技法_4拉升技法

写在前面&#xff1a; 文中知识内容来自书籍《同花顺炒股软件从入门到精通》 目录 1. 拉升时机的选择 1&#xff09;大盘走势稳健时 2&#xff09;重大利好出台前后 3&#xff09;进行一次凶狠的打压之后 4&#xff09;大市偏弱时 5&#xff09;图形及技术指标修好之时 …

FPGA开发(4)——AXI_LITE总线协议

一、AXI总线简介 对于axi总线的学习我主要是参考了赛灵思的ug1037文档以及arm的INI0022D手册&#xff0c;对其中的内容做了总结。 AXI是amba总线的一种&#xff0c;包含三种&#xff0c;axi full、axi lite和axi stream。 AXI工作&#xff1a;axi接口包含了五组通道&#xf…

如何快速用一条命令配置好本地yum源(6/7/8版本)

一&#xff0c;挂载ISO安装镜像 挂载方式分两种&#xff1a; 1.上传iso安装镜像到服务器主机的指定目录&#xff0c;比如/setup/os为例 mount -o loop /setup/os/iso镜像包名称 /mnt 2.直接虚拟机或者物理主机挂载iso安装镜像 mount /dev/cdrom /mnt mount/dev/sr0 /mnt 3.挂载…

【计算机网络】网络层:路由器的构成

路由器工作在网络层&#xff0c;用于互连网络&#xff0c;主要工作是转发分组。 把某个输入端口收到的分组&#xff0c;按照分组要去的目的网络&#xff0c;把该分组从路由器的某个合适的输出端口转发给下一跳路由器。 &#xff08;根据目的网络的IP地址转发分组&#xff09;…

[附源码]java毕业设计学生档案管理系统

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

使用Umi 遇到的错误

今天想使用ui&#xff0c;出现了很多错误&#xff0c;刚开始安装的时候没有一点事&#xff0c;就是运行的时候报错&#xff0c;好像和umi版本不匹配了&#xff0c;后来又把umi删除了又安装一遍&#xff0c;然后还是运行不了&#xff0c;后来我又把umijs/preset-ui卸了&#xff…

通过制作4个游戏学习Python

前言 学习编程最好玩的方法 你会学到什么 &#xff08;文末送读者福利&#xff09; 您将学习如何有效地使用Python 您将创建一个python游戏组合 你将学会如何管理好大型项目 你将学习面向对象编程 您将学习并实现高级Python特性 你将对Python有一个透彻的理解 类型:电…

Spark并行度和任务调度

文章目录并行度如何设置并行度如何规划我们自己群集环境的并行度&#xff1f;Spark的任务调度并行度 Spark之间的并行就是在同一时间内&#xff0c;有多少个Task在同时运行。并行度也就是并行能力的设置&#xff0c;假设并行度设置为6&#xff0c;就是6个task在并行跑&#xf…

蒙特卡洛原理及实例(附Matlab代码)

文章目录一、理论基础1.1 伯努利大数定理1.2 辛钦大数定理1.3 切比雪夫大数定理1.4 三者区别和联系二、蒙特卡洛法2.1 蒙特卡洛的起源2.2 蒙特卡洛的解题思路2.2 蒙特卡洛法的应用三、几个小栗子3.1 求解定积分3.1.1 解析法3.1.2 蒙特卡洛法3.2 求解六边形面积3.2.1 解析法3.2.…

[附源码]SSM计算机毕业设计基于的高校学生考勤管理系统JAVA

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

支持向量机

目录 支持向量机 0. 由来 1. 核心思想 2. 硬间隔支持向量机 2.1 间隔最大化 2.1.1 函数间隔2.1.2 几何间隔2.1.2 间隔最大化 2.2 转换为拉格朗日对偶问题 2.2.1 拉格朗日对偶问题2.2.2 将问题转换为拉格朗日对偶问题 3. 软间隔支持向量机 4. 泛函基础 4.1 度量&#xff…

Flutter 8 个优秀动画 Packages

Flutter 8 个优秀动画 Packages 前言 动画对于使移动应用程序的用户界面感觉自然流畅至关重要。加上交互式元素和平滑的过渡&#xff0c;它们使应用程序简单易用。 正文 Flutter Animate 组件 Package https://pub.dev/packages/flutter_animate 一个 performant 库&#xff0c…

springboot simple (9) springboot jpa(Hibernate)

返回目录 1 JPA Hibernate Hibernate是一个全自动的ORM框架&#xff08;Object Relational Mapping ,对象关系映射&#xff09;。 Spring Data JPA&#xff1a; 是Spring Data的子模块&#xff0c;JPA默认使用hibernate作为ORM实现。 2 springboot继承Hibernate 第1步&…

【Servlet】7:监听器和过滤器的原理和应用

目录 | 监听器 监听器 基本概述 ServletContextListener监听器 ServletContextAttributeListener监听器 监听器的应用场景 | 过滤器 过滤器 基本概述 过滤器 实现步骤 过滤器 应用场景 本文章属于后端全套笔记的第三部分 &#xff08;更新中&#xff09;【后端入门到入…

leetcode 494.目标和 动态规划背包问题 (c++版本)

题目描述 说白了就是让一部分数减去剩下的一部数使得差值为target&#xff0c;计算有多少中组合的方法 下面来个数学公式推导一下 leftrightsumleft−righttargetleftsum−lefttargetleft(sumtarget)/2leftright sum\\ left-righttarget\\ leftsum-lefttarget\\ left(sumtarge…

用户行为分析-如何用数据驱动增长

用户行为分析-如何用数据驱动增长 2022-11-22 看完书才知道是 GrowingIO 公司出的一本书&#xff0c;干货还是挺多的。 第一章从商业进化的角度认识用户行为数据的重要性&#xff0c;帮助大家了解什么是用户行为数据&#xff0c;以及用户行为数据怎么发挥价值。接着四章详细…

【操作系统】2.2 操作系统的调度

2.2.1 操作系统之处理机调度的概念及层次 2.2.1操作系统之处理机调度的概念及层次_StudyWinter的博客-CSDN博客_操作系统调度的层次 高级调度&#xff08;作业调度&#xff09;&#xff1a;外存-》内存 中级调度&#xff08;内存调度&#xff09;&#xff1a;外存-》内存 低…