JUC AQS(AbstractQueuedSynchronizer)

文章目录

  • AQS (AbstractQueuedSynchronizer^1.5+^)
  • CLH 锁队列
    • AbstractQueuedSynchronizer 成员变量说明
    • AbstractQueuedSynchronizer.Node 源码
    • CLH 队列原理图
      • 入队逻辑方法
      • 出队逻辑方法
  • 继承 AQS 需要实现的几个方法
  • AQS 对象序列化
  • ReentrantLock 源码解析
    • ReentrantLock Lock 加锁过程源码解析
    • ReentrantLock unlock 解锁过程源码解析
  • 总结

AQS (AbstractQueuedSynchronizer1.5+

按照类名直译过来就是抽象队列同步器。用于实现依赖先进先出(FIFO)等待队列的阻塞式锁或相关的同步器。它是这些锁、同步器的基础,提供了一个整体框架实现,我们只需要实现 AQS 的几个方法就能实现自己的 ReentrantLock

CLH 锁队列

CLH(Craig, Landin, Hagersten 三位大神发明的队列,其本身是一个单向链表,但在 AQS 中是使用的双向链表实现),它是 AQS 实现等待队列的基础

AbstractQueuedSynchronizer 成员变量说明

/*** 等待队列的头,懒初始化的,在初始化后,只能通过 setHead 方法修改* 如果 head 节点的存在,则 head 的状态 waitStatus 不会是 Node.CANCELLED 的* 出队,就是从 head 出*/private transient volatile Node head;/*** 等待队列的尾部,懒初始化,初始化后只能通过 enq 方法添加新的等待节点*/private transient volatile Node tail;/*** 同步器的状态* 通过 getState、setState、compareAndSetState 方法操作* */private volatile int state;

AbstractQueuedSynchronizer.Node 源码

static final class Node {/** 标识节点为共享模式 */static final Node SHARED = new Node();/** 标识节点为独占模式 */static final Node EXCLUSIVE = null;/** waitStatus 的值为1,标明为当前节点已取消 */static final int CANCELLED =  1;/** waitStatus 的值为 -1,标明当前节点的后续节点需要取消等待(调用unpark)*/static final int SIGNAL    = -1;/** waitStatus 的值为 -2,标明后续节点在 condition 上等待*/static final int CONDITION = -2;/*** waitStatus 的值为 -3,通知后续节点当前节点当前已 releaseShared */static final int PROPAGATE = -3;/*** 状态字段,取值如下:*   SIGNAL -1:     此节点之后的节点为阻塞(park),当此节点在释放后,后续的节点应该upark*   CANCELLED 1:  此节点由于超时或中断而被取消,具有取消节点的线程永远不会再阻塞。*   CONDITION -2:  此节点当前位于条件队列中。在传输之前,它不会被用作同步队列节点,此时状态将被设置为0。 *   PROPAGATE -3:  需要保证传播 releaseShared 的其他节点*   0:          以上状态之外的值** 非负数字不需要发送信号** 普通同步节点初始化为 0* 条件节点初始化为 CONDITION*/volatile int waitStatus;/*** 当前节点的前一个节点,在出队时被设置为 null*/volatile Node prev;/*** 当前节点的 next 节点*/volatile Node next;/*** 进入该节点的线程*/volatile Thread thread;/*** 下一个等待条件的节点或特殊节点 SHARED*/Node nextWaiter;/*** 判断节点是否在共享模式下等待*/final boolean isShared() {return nextWaiter == SHARED;}/*** 返回 prev 节点,如果为空,这抛出 NullPointerException ** @return the predecessor of this node*/final Node predecessor() throws NullPointerException {Node p = prev;if (p == null)throw new NullPointerException();elsereturn p;}Node() {    // Used to establish initial head or SHARED marker}Node(Thread thread, Node mode) {     // Used by addWaiterthis.nextWaiter = mode;this.thread = thread;}Node(Thread thread, int waitStatus) { // Used by Conditionthis.waitStatus = waitStatus;this.thread = thread;}}

CLH 队列原理图

在这里插入图片描述

CLH 队列是双向链表的实现,只有一个地方比较特殊,就是入队时,第一个入队节点之前会加入一个没有设置节点对应的线程和mode模式的 new Node() 节点,这个主要是因为出队时的逻辑限制,CLH 队列入队和出队主要对应如下几个方法

入队逻辑方法

    // 没有获得锁,当前线程加入队列排队// 实际就是 CHL 队列的入队逻辑private Node addWaiter(Node mode) {// 通过当前线程和排队模式新建 Node 节点// Node.EXCLUSIVE 为独占模式; Node.SHARED 为共享模式Node node = new Node(Thread.currentThread(), mode);// 当前队列的队尾元素Node pred = tail;// 当前队列的队尾不为 nullif (pred != null) {// 设置当前节点的 prev(前置节点) 为 tailnode.prev = pred;// CAS 设置 pred 为 node(就是将当前 node 设置为 tail)if (compareAndSetTail(pred, node)) {pred.next = node;return node;}}// 当前队尾为 null 执行 enq 逻辑enq(node);return node;}private Node enq(final Node node) {for (;;) {// t 指向队尾元素Node t = tail;// 当前队尾为 nullif (t == null) { // Must initialize// 新建一个 Node 设置为 headif (compareAndSetHead(new Node()))tail = head;// 这里没有 return 会继续循环下一次} else {// 当前节点的 prev 指向队尾元素node.prev = t;// 设置 t 为 当前节点if (compareAndSetTail(t, node)) {// 设置 t 的 next 为当前节点t.next = node;return t;}}}}

出队逻辑方法

    final boolean acquireQueued(final Node node, int arg) {// 是否失败,默认为 trueboolean failed = true;try {// 是否被打断,默认否boolean interrupted = false;for (;;) {// 获取当前节点的前驱节点(前驱节点为空将抛异常)// 这也是为什么 CLH 队列第一个节点为 new Node() 的原因// new Node只是一个占位节点final Node p = node.predecessor();// 如果前驱节点为 head 节点// 且 tryAcquire 成功,则前驱节点出队if (p == head && tryAcquire(arg)) {// 设置 head 为当前节点setHead(node);p.next = null; // help GCfailed = false;// 返回否 false,将不会执行 selfInterrupt()// 即不会执行当前线程的 interrupt 方法return interrupted;}// shouldParkAfterFailedAcquire 检查 prev 的 waitStatus 状态// 为 SIGNAL 才返回 true// parkAndCheckInterrupt() park 当前线程(开始阻塞)// 当执行的线程 unpark 后,返回当前线程是否被打断if (shouldParkAfterFailedAcquire(p, node) &&parkAndCheckInterrupt())interrupted = true;}} finally {// 如果出队失败,则取消掉当前节点if (failed)cancelAcquire(node);}}

出队逻辑和入队逻辑都是 AQS 类的方法。

继承 AQS 需要实现的几个方法

    /*** 尝试以独占模式获取锁** 执行获取锁的线程都会调用此方法,如果此方法返回失败,则将尚未排队的线程加入队列排队,直到收到其他线程发出的释放信号。* 此方法用于实现 Lock.tryLock() 方法** 默认实现是抛出 UnsupportedOperationException 异常** @param arg 原则上是可以表示任何内容的值。主要用于判断是否可以获取、以及是否需要等待等* @return true:表示可以获取锁,false 表示无法获取锁* @throws IllegalMonitorStateException 非法的同步器状态* @throws UnsupportedOperationException 如果不支持独占模式*/protected boolean tryAcquire(int arg) {throw new UnsupportedOperationException();}/*** 尝试释放独占锁** 所有的执行线程都会调用此方法来释放锁*** 默认实现是抛出 UnsupportedOperationException 异常** @param arg 原则上是可以表示任何内容的值。主要用于判断是否可以获取、以及是否需要等待等* @return true:表示锁释放成功,其他等待的线程可以尝试获取了,false 表示释放锁失败* @throws IllegalMonitorStateException 非法的同步器状态* @throws UnsupportedOperationException 如果不支持独占模式*/protected boolean tryRelease(int arg) {throw new UnsupportedOperationException();}/*** 尝试以共享模式获取锁** 执行获取锁的线程都会调用此方法,如果此方法返回失败,则将尚未排队的线程加入队列排队,直到收到其他线程发出的释放信号。** 默认实现抛出:UnsupportedOperationException 异常** @param arg 原则上是可以表示任何内容的值。主要用于判断是否可以获取、以及是否需要等待等* @return 小于 0,表示在共享模式下获取失败* 		   等于 0,表示在共享模式下获取成功,且没有后续的共享模式能够成功获取* 		   大于 0,表示在共享模式下获取成功,且后续的共享模式获取也能够成功。在此情况下,后续的等待线程必须检查可用性。* @throws IllegalMonitorStateException 非法的同步器状态* @throws UnsupportedOperationException 如果不支持共享模式*/protected int tryAcquireShared(int arg) {throw new UnsupportedOperationException();}/*** 尝试释放共享锁** 所以执行的线程都会执行此方法来释放共享锁** 默认实现抛出:UnsupportedOperationException 异常** @param arg 原则上是可以表示任何内容的值。主要用于判断是否可以获取、以及是否需要等待等* @return 成功释放,且其他等待线程可以尝试获取锁,则返回 true,否则返回 false* @throws IllegalMonitorStateException 非法的同步器状态* @throws UnsupportedOperationException 如果不支持共享模式*/protected boolean tryReleaseShared(int arg) {throw new UnsupportedOperationException();}/*** Returns 如果当前线程持有锁,则返回 true.  * ConditionObject 的 signal 方法在调用时都会调用此方法** 此方法仅在 AbstractQueuedSynchronizer 内部调用。* 用于 ConditionObject ,如果不使用 ConditionObject 可以不用实现 ** @return 如果当前线程持有锁,则返回 true. * @throws UnsupportedOperationException 如果 ConditionObject 不支持*/protected boolean isHeldExclusively() {throw new UnsupportedOperationException();}

AQS 对象序列化

AQS 的序列化只存储底层的原子整数维护状态,因此反序列化的对象具有空线程队列。所以我们需要定义一个 readObject 方法,该方法在反序列化时将其恢复到已知的初始状态。

ReentrantLock 源码解析

ReentrantLock 涉及到的源码继承关系图

在这里插入图片描述

这里的红色线都表示为静态内部类,ReentrantLock 有 Sync 和 FairSync、NonFairSync 三个静态内部类,AQS 有 Node 和 ConditionObject 两个静态内部类。

ReentrantLock Lock 加锁过程源码解析

注:这里是使用的 new ReentrantLock() 方式,其实就是 NonfairSync(非公平锁的方式)

请添加图片描述

看不清可以点击大图查看,也可以通过本文最上面的链接下载图片来查看,由于平台限制(最5M)我本地画的高清图片无法上传。

ReentrantLock unlock 解锁过程源码解析

请添加图片描述

关于源码的解析,要写成文章,真是太难了,这里也只是把相关的逻辑做成图片的形式更方便梳理其中的逻辑,图中也将相关的源码方法都贴了上去,并做了相关的逻辑解释,这些解释并不一定准确,希望对大家有所帮助

总结

这里我们只分析了 ReentrantLock 的 lock、unlock 方法的源码分析,ReentrantLock 的其他方法以及 ReentrantReadWriteLock、CountDownLatch、Semaphore 等实现都与我们分析的这些差不太多,我们就不再一一的做解析了,这里我们先总结一下,源码分析的过程与方法,最后再总结一下结论吧。

  1. CLH 队列由双向链表实现
  2. CLH 队列的元素第一个 head 节点为 new Node()
  3. CLH 队列的每个元素包含要执行的线程 thread、锁的共享方式 mode、前驱节点 prev、后继节点 next、状态 waitStatus
  4. AQS 底层实现等待的方法是 LockSupport.park 和 LockSupport.unpark

建议大家最好是自己通过 IDE debug 走一走 lock、unlock 或者 tryLock 等方法的源码,这样才能更有印象。

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

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

相关文章

环境监测LoRa网关解决方案应用空气质量监控

随着全球工业化和城镇化的快速发展,空气质量问题越来越受到关注。环境监测技术的发展,可以有效地帮助我们监测和改善空气质量。而LoRa网关则是一种可以帮助我们实现远距离、低功耗通信的无线通信技术,它的应用可以为空气质量监测提供解决方案…

【Matplotlib】基础设置之文本标签03

处理文本 import matplotlib.pyplot as plt import numpy as np %matplotlib inlinematplotlib 对文本的支持十分完善,包括数学公式,Unicode 文字,栅格和向量化输出,文字换行,文字旋转等一系列操作。 基础文本函数 …

5.1 QThread的两种使用方式

5.1 QThread的两种使用方式 QThread类用于创建和管理线程,它并不是线程本身。通过使用QThread,我们可以在应用程序中实现并发执行的任务,从而提高应用程序的性能和响应能力,能够有效地利用CPU资源,提高程序运行效率。且QThread创建和管理线程的方式是独立于平台的,不管是…

学习笔记240102 --- 表单无法输入,是否data中没有提前声明导致的

前端框架 &#xff1a;vue2.x 第三方ui组件&#xff1a;ElementUI 操作系统&#xff1a;windows 浏览器&#xff1a;chrome 谷歌 问题描述 表单使用中&#xff0c;没有在data中提前声明参数&#xff0c;当数据回显时&#xff0c;表单无法输入 <el-form :model"queryPa…

制造企业如何打破“信息孤岛”,跑赢从制造到“智造”的破局之路?

随着工业4.0时代到来&#xff0c;制造业乘上了智能制造发展的快车&#xff0c;但“乘客”却偏少。普华永道发布的《2022年数字化工厂转型调研报告》中指出&#xff0c;来自23个国家和地区的700多家受访企业中&#xff0c;只有10%的企业已经完成数字化转型计划或处于转型最后阶段…

Sourcetree安装和配置

先了解Sourcetree是用来做什么的 简单说就是一个有可视化界面的Gti 用途&#xff1a; &#xff08;1&#xff09;克隆(clone)&#xff1a;从远程仓库URL加载创建一个与远程仓库一样的本地仓库 提交(commit)&#xff1a;将暂存文件上传到本地仓库&#xff08;我们在Finder中对本…

详细分析MybatisPlus中的orderBy、orderByDesc、orderByAsc函数

目录 前言1. 概念2. API示例3. 实战 前言 实战中学习并进行补充该类的源码以及应用 1. 概念 在 MyBatis-Plus 中&#xff0c;orderBy、orderByDesc 和 orderByAsc 是用于构建 SQL 查询语句中的 ORDER BY 子句的方法。 这些方法都是通过 QueryWrapper 类的实例来调用的&…

【Java】实验三 抽象类与接口

实验名称 实验三 抽象类与接口 实验目的 1. 深刻理解抽象类、接口的意义。 2. 熟练掌握抽象类和接口的定义、继承抽象类以及实现接口的方法。 3. 理解和掌握多态。 实验内容 &#xff08;一&#xff09;抽象类实验&#xff1a;项目源码中新建一个ahpu.shape的包&a…

低代码:实现数据可视化的强大助手

随着数据在企业中的价值越来越受到重视&#xff0c;数据可视化成为了决策者和业务专家们必备的工具。然而&#xff0c;传统的数据可视化开发过程常常繁琐且耗时&#xff0c;限制了其在应用中的广泛应用。低代码平台的出现&#xff0c;为实现高效的数据可视化提供了新的解决方案…

切换node.js不同版本

切换node.js不同版本 因新项目用到vite4创建项目&#xff0c;输入命令后报错&#xff0c;经查询得知是node版本过低导致&#xff0c;所以需要升级node版本&#xff0c;但是又有老的项目需要维护&#xff0c;因此需要多个版本的node使用需求。 流程&#xff1a; 卸载原有的node…

实验笔记之——Linux实现COLMAP

之前博客跑instant-NGP的时候&#xff0c;除了用官方的数据集&#xff0c;用自己的数据则是通过手机采集&#xff0c;同时获得pose与image。但是这种获取的方式对于3D gaussian而言&#xff0c;并不支持对应的数据格式&#xff0c;为此采用COLMAP来根据image获取pose&#xff0…

Python条件语句与运算符优先级详解,python学习必看

文章目录 Python 条件语句Python运算符优先级关于Python技术储备一、Python所有方向的学习路线二、Python基础学习视频三、精品Python学习书籍四、Python工具包项目源码合集①Python工具包②Python实战案例③Python小游戏源码五、面试资料六、Python兼职渠道 Python 条件语句 …

springcloud之集成nacos config

写在前面 源码 。 本文看下如下集成nacos config组件。 1&#xff1a;常见配置方式分析 我们先来看下常见的配置方式都有哪些&#xff0c;以及其有什么优点和缺点。 硬编码 优点&#xff1a;hardcode&#xff0c;除了开发的时候快些&#xff0c;爽一下&#xff0c;有个屁优…

洗地机什么牌子最好?家用洗地机推荐指南

随着人们对健康和卫生的关注日益增长&#xff0c;洗地机成为了现代家庭清洁的必备工具。然而&#xff0c;在市场上琳琅满目的洗地机品牌中&#xff0c;洗地机哪个品牌最好最实用呢?这是消费者最为关心的问题。现本文将为您介绍几个备受推崇的洗地机品牌&#xff0c;帮助您在众…

Linux进程以及计划任务

一&#xff1a;程序&#xff1a; 1.什么是程序&#xff1f; 执行特定任务的一串代码 &#xff0c;是一组计算机能识别和执行的指令&#xff0c;运行于电子计算机上&#xff0c;满足人们某种需求的信息化工具 用于描述进程要完成的功能&#xff0c;是控制进程执行的指令集 二…

论文查重降重写成大白话可以吗

大家好&#xff0c;今天来聊聊论文查重降重写成大白话可以吗&#xff0c;希望能给大家提供一点参考。 以下是针对论文重复率高的情况&#xff0c;提供一些修改建议和技巧&#xff0c;可以借助此类工具&#xff1a; 论文查重降重&#xff1a;用大白话解析 一、引言 写论文是每个…

进程的韵律:探索计算机世界中的动态舞台

这里写目录标题 进程定义进程的组成进程与程序区别进程与线程区别进程特点进程控制结尾 进程定义 进程&#xff1a;一个具有一定功能的程序在一个数据集合上的一次动态执行过程。 进程是指正在运行的程序&#xff0c;它是操作系统进行资源分配和调度的基本单位。在计算机中&…

红日靶场第一关 attck

之前因为事情耽搁了&#xff0c;今天争取把第一关红日靶场完成 目前找到了关于外网服务器的网址 之前有过扫描目录得知了登陆界面 和爆破得到的密码 目前我们的想法是把病毒上传到网页当中&#xff0c;所以我们应该找个文件注入点 但是再次之前 我们需要找到网页的绝对路径 …

生成式AI:软件工程的未来伙伴

随着技术不断进步&#xff0c;软件工程正在经历一场革命性的变革。从最初的穿孔卡片和汇编语言编程&#xff0c;到现代集成开发环境和高级编程语言&#xff0c;软件工程已经走过了一条漫长的路。现在&#xff0c;生成式人工智能(AI)正打开新的篇章&#xff0c;不仅对传统的编码…

解决SyntaxError: future feature annotations is not defined,可适用其他包

方法&#xff1a;对报错的包进行降级 pip install tikzplotlib0.9.8site-packages后面是使用pip install安装的包&#xff0c;根据这个找到报错的包 想法来源&#xff1a; 环境是python3.6&#xff0c;完全按照作者要求进行环境配置&#xff0c;但仍报错。 我在网上找的解决…