Java并发编程第12讲——cancelAcquire()流程详解及acquire方法总结

news2024/11/15 15:54:30

上篇文章介绍了AQS的设计思想以及独占式获取和释放同步状态的源码分析,但是还不够,一是感觉有点零零散散,二是里面还有很多细节没介绍到——比如cancelAcquire()方法(重点),迫于篇幅原因,今天就把它放到这篇文章里,继续深入AQS!

一、acquire方法

源码的分析在上一篇文章,感兴趣的同学可以去看一下,我的建议是两篇文章一起看。

1.1 几个状态(重点)

ps:waitStatus>0说明等待状态时CANCELLED,waitStatus<0为其它状态。

//表示线程已取消:由于在同步队列中等待的线程等待超时或中断
//需要从同步队列中取消等待,节点进入该状态将不会变化(即要移除/跳过的节点)
static final int CANCELLED =  1;
//表示后继节点处于park,需要唤醒:后继节点的线程处于park,而当前节点
//的线程如果进行释放或者被取消,将会通知(signal)后继节点。
static final int SIGNAL = -1;
//表示线程正在等待状态:即节点在等待队列中,节点线程在Condition上,
//当其他线程对Condition调用signal方法后,该节点会从条件队列中转移到同步队列中
static final int CONDITION = -2;
//表示下一次共享模式同步状态会无条件地传播下去
static final int PROPAGATE = -3;
//节点的等待状态,即上面的CANCELLED/SIGNAL/CONDITION/PROPAGATE,初始值为0
volatile int waitStatus;

1.2 acquire()流程图及分析

基本流程描述:

  • 调用子类重写的tryAcquire方法尝试获取同步状态,若成功则返回,反之进入addWaiter方法
  • 基于当前线程新建一个Node节点,若队列不为空则将Node节点CAS操作挂在队列尾部,队列为空或CAS失败进入enq方法
  • 查看队列是否已经初始化,若没有则优先初始化队列(自旋),随后将Node节点以CAS的方式插入队列,CAS失败则继续自旋,反之进入acquireQueued方法
  • 若Node的前驱节点为头节点,且再次tryAcquire()成功,则将Node设置为头节点,并结束自旋。若两个条件任意一个失败则进入shuoldParkAfterFailedAcquire方法
  • 若Node的前驱节点等待状态为SIGNAL,则调用parkAndCheckInterrupt方法将当前线程阻塞,若当前线程的中断状态为ture则将acquireQueued的返回值置为true,并继续自旋;反之则判断Node的前驱节点的等待状态是否为CANCELLED,若不是则CAS尝试将Node的前驱节点等待状态改为SIGNAL,并继续自旋;若是则说明这个前驱节点无效,直接跳过该节点并找一个非CANCELLED节点作为Node的前驱节点,并结束自旋(acquireQueued方法结束)。
  • acquireQueued方法返回ture则说明当前线程需要被中断(也就是Node节点的前面还有节点在排队,还没轮到Node节点)。
  • 若在acquireQueued方法中出现异常,则会调用cancelAcquire方法进行该节点的取消逻辑,这也是我们今天的重点,下面会具体分析。

二、cancelAcquire方法

上篇文章在分析它的源码时就感觉有点懵懵的,很多地方都不太理解(那面试的时候怎么跟面试官battle啊😁),那么今天就深入的分析一下。

2.1 源码

再次附上源码,方便观看。

在acquireQueued方法中出现异常会走cancelAcquire方法取消正在进行acquire的尝试,以防止死锁或长时间的等待。这里我把它分为两个红框,下面图解流程时会用到。

2.2 流程图 

解释一下颜色代表的意思:

  • 紫色——方法开始和结束。
  • 橙色——断开与取消结点联系的执行逻辑。
  • 黄色——node结点为tail结点执行的逻辑。
  • 蓝色——node结点不为tail结点执行的逻辑(为head结点的后继结点或中间结点)。
  • 其它——一般逻辑。

2.3 Node为尾节点

初始状态:即N1为Node节点

执行第一个红框:  

  • 将Node结点Thread置空。
  • Node节点的前驱结点N2的等待状态为CANCELLED,所以断开N1到N2的联系,并与N3建立联系。
  • 将pred指向N3结点,predNext指向N2(这里不是node节点)并把Node结点的等待状态置为CANCELLED。

 执行第二个红框:

  • 将pred设置为tail节点。
  • 断开N3到N2结点的联系。
  • 最后N1和N2节点会被GC回收。

2.4 node为中间节点

2.4.1 N3节点取消流程

初始状态:Node节点为中间节点,既不是tail节点,也不是head节点的后继节点。

执行第一个红框:

  • 将node的Thread置为null。
  • pred指向N4,preNext指向N3,也就是node节点。
  • 将node的等待状态置为CANCELLED。

 执行第二个红框:

  • next指向node节点的后继节点N2。
  • CAS将N4的后继节点置为N2。
  • Node的后继节点指向自己。

注意:此时N2对N3的指针还没有断开,这就意味着N2并不会被GC回收,那么N2对N3的引用为什么不断开?当时作者也有点不理解,直到...假设N2也调用了cancelAcquire方法,下面一起来看一下。

2.4.2 继N3取消后N2取消逻辑

初始状态:N2为取消节点,这里就不作解释了。

 

执行第一个红框:

 执行第二个红框:

  • N2执行完取消逻辑后,N3就会被GC回收。这里我们思考一下如果N3取消逻辑执行完之后就断开N2到N3的prev指针会发生什么?很简单,N2就遍历不到它前面的结点了,所以N3在取消时保留了N2到N3的指针。
  • 再思考一个问题,N3被GC回收了,要是N2执行取消逻辑后,没有后继结点取消了,那N2如何被GC回收回收呢?

2.4.3 继N2取消后N2被GC回收逻辑

N2被GC回收其实是在N4结点成功获取同步状态且释放同步状态,并唤醒其后继节点N1时完成的,我们来看一下。

此时的N4为head结点,N1为node结点。

我们再来回顾下acquireQueued方法。  

注意此时N1的prev还是N2,所以会执行shouldParkAfterFailedAcquire方法。

至此N2也会被GC回收,T4继续自旋,直到成功获取同步状态或出现异常。

  • 所以取消节点被GC回收有两种情形:一是后继结点取消,二是后继结点被唤醒。

2.5 node为头节点的后继结点

2.5.1 N3的取消逻辑

初始状态:

执行第一个红框:  

执行第二个红框:会调用unparkSuccessor方法  

 

2.5.2 继N3取消逻辑N2被唤醒

N2被唤醒尝试获取同步状态,也就是执行acquireQueued方法。

初始状态:

执行acquireQueued方法:

  • 参考2.4.3 继N2取消后N2被GC回收逻辑,会调用shuldParkAfterFailedAcqquire断开N2到N3的指针。
  • 然后回将pred的后继指针指向N2。

  • 返回false,T2接着自旋,假设tryAcquire成功,执行setHead方法。

  • 将head指针执行N2。
  • 将node结点thread置为null。
  • 将node的prev指针置为null。

接着有一段神奇的代码,将原head指向N2的next指针断开。  

至此,原head结点完全脱离队列,等待GC回收。但不执行p.next=null似乎也符合GC的条件,那为什么要执行呢?

如果不执行p.next=null,垃圾回收器也能自动检测并回收,但这个过程相比较而言会更耗时。也就是如果p.next仍然引用N2,那么可能会遍历整个链表来标记垃圾,这就会花费更多的时间和资源才能发现并回收p结点。执行p.next=null可以明确地告诉垃圾回收器,与p关联的结点均为垃圾,并加速回收过程。

三、总结

  • cancelAcquire方法就负责取消结点的逻辑,即将前置结点等待状态、线程置空、非取消和后置非取消结点联系起来、或在特定场景下唤醒后继结点。
  • shouldParkFailedAcquire方法的作用就是挂起线程和队列调整进而GC回收取消节点,即当前结点前驱节点的等待状态为SIGNAL时,返回true,将当前线程挂起。反之会调整队列将取消结点进行GC回收。

还有setHead方法添加头节点(初始化队列)和删除头节点,p.next=null加速GC回收等等,每个方法甚至每段代码都配合的十分精妙,我只能说一句🐂🍺。

End:希望对大家有所帮助,如果有纰漏或者更好的想法,请您一定不要吝啬你的赐教🙋。 

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

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

相关文章

[超详细]基于YOLO&OpenCV的人流量统计监测系统(源码&部署教程)

1.图片识别 2.视频识别 [YOLOv7]基于YOLO&#xff06;Deepsort的人流量统计系统(源码&#xff06;部署教程)_哔哩哔哩_bilibili 3.Deepsort目标追踪 &#xff08;1&#xff09;获取原始视频帧 &#xff08;2&#xff09;利用目标检测器对视频帧中的目标进行检测 &#xff08…

MAX/MSP SDK学习04:Messages selector的使用

其实消息选择器在simplemax示例中就接触到了&#xff0c;但这文档非要讲那么抽象。目前为止对消息选择器的理解是&#xff1a;可判断接收过来的消息是否符合本Object的处理要求&#xff0c;比如加法对象只可接收数值型的消息以处理&#xff0c;但不能接收t_symbol型的消息&…

2024电脑录屏软件排行第一Camtasia喀秋莎

真的要被录屏软件给搞疯了&#xff0c;本来公司说要给新人做个培训视频&#xff0c;想着把视频录屏一下&#xff0c;然后简单的剪辑一下就可以了。可谁知道录屏软件坑这么多&#xff0c;弄来弄去头都秃了&#xff0c;不过在头秃了几天之后&#xff0c;终于让我发现了一个值得“…

ck 配置 clickhouse-jdbc-bridge

背景 ck可以用过clickhouse-jdbc-bridge技术来直接访问各数据库 安装配置 需要准备的文件 clickhouse-jdbc-bridge https://github.com/ClickHouse/clickhouse-jdbc-bridge 理论上需要下载源码然后用mavne打包&#xff0c;但提供了打包好的&#xff0c;可以推测用的是mave…

层层剥开Android14升级后异常弹框的神秘面纱

本篇文章将会通过研究源码的方式给您讲述Android系统升级到Android14后出现的两个异常弹框并给出消除它们的方案。闲话少叙&#xff0c;我们开始。 问题描述 在Android 14升级后&#xff0c;出现两个弹窗的异常情况。这里是异常的截图&#xff1a; 接下来&#xff0c;我们对这…

1-verilog的串行滤波器FIR实现

verilog的串行滤波器FIR实现 1&#xff0c;RTL代码2&#xff0c;RTL原理框图3&#xff0c;测试代码4&#xff0c;输出FIR滤波器的波形 参考文献: 1&#xff0c;基于FPGA的串行FIR滤波器设计与实现 2&#xff0c;FPGA实现FIR滤波器 1&#xff0c;RTL代码 timescale 1ns / 1ps /…

子虔与罗克韦尔自动化合作 进博会签约自动化净零智造联创中心

11月6日进博会现场&#xff0c;漕河泾罗克韦尔自动化净零智造联创中心合作协议签约暨合作伙伴&#xff08;第一批&#xff09;授牌仪式举办&#xff0c;子虔科技作为联创中心合作伙伴签约&#xff0c;携手共建智能制造&#xff0c;引领行业可持续发展。 图示&#xff1a;子虔科…

从0开始学习JavaScript--深入理解JavaScript的async/await

JavaScript的异步编程在过去经历了回调地狱、Promise的引入&#xff0c;而今&#xff0c;通过async/await&#xff0c;让我们获得了更加优雅、可读性更高的异步编程方式。本文将深入探讨async/await的概念、用法&#xff0c;并通过丰富的示例代码展示其在实际应用中的威力。 理…

SPDK NVMe-oF target多路功能介绍

基本概念 SPDK NVMe-oF target multi-path是基于NVMe协议的multi-path IO和namespace sharing功能。 NVMe multi-path IO指的是两个或多个完全独立的PCI Express路径存在于一个主机和一个命名空间。 而namespace 共享是两个或多个主机使用不同的NVMe控制器访问一个shared na…

2023.11.22使用flask做一个简单的图片浏览器

2023.11.22使用flask做一个简单的图片浏览器 功能&#xff1a; 实现图片浏览&#xff08;翻页&#xff09;功能 程序页面&#xff1a; 程序架构&#xff1a; 注意&#xff1a;在flask中常会使用src“{{ url_for(‘static’, filename‘images/’ image) }}”&#xff0c…

利用ros实现单片机通讯(转载)

我觉得如果使用这个人的micro_ros通信协议&#xff0c;就不用再去Ubuntu或者Windows上面自己写驱动程序了&#xff0c; 利用micro_ros实现esp32与ros2的通讯 Tianci ​ 天津大学 工学博士 参考&#xff1a;https://github.com/micro-ROS/micro_ros_arduino https://blog.cs…

【开源】基于Vue和SpringBoot的服装店库存管理系统

项目编号&#xff1a; S 052 &#xff0c;文末获取源码。 \color{red}{项目编号&#xff1a;S052&#xff0c;文末获取源码。} 项目编号&#xff1a;S052&#xff0c;文末获取源码。 目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 数据中心模块2.2 角色管理模块2.3 服…

森林之子/Sons Of The Forest V42457 资源分享

游戏介绍&#xff1a; 视频介绍&#xff1a; 森林之子 资源分享 这里是引用 你被派到了一座孤岛上&#xff0c;寻找一位失踪的亿万富翁&#xff0c;结果却发现自己深陷被食人生物占领的炼狱之地。你需要制作工具和武器、建造房屋&#xff0c;倾尽全力生存下去&#xff0c;无论…

MySQL之BETWEEN AND包含范围查询总结

一、时间范围 查询参数格式与数据库类型相对应时&#xff0c;between and包含头尾&#xff0c;否则依情况 当数据库字段中存储的是yyyy-MM-dd格式&#xff0c;即date类型&#xff1a; 用between and查询&#xff0c; 参数yyyy-MM-dd格式时&#xff0c;包含头尾&#xff0c;相当…

【Flink】Process Function

目录 1、ProcessFunction解析 1.1 抽象方法.processElement() 1.2 非抽象方法.onTimer() 2、Flink中8个不同的处理函数 2.1 ProcessFunction 2.2 KeyedProcessFunction 2.3 ProcessWindowFunction 2.4 ProcessAllWindowFunction 2.5 CoProcessFunction 2.6 ProcessJo…

C++ 多态和虚函数详解

本文章内容来源于C课堂上的听课笔记 多态基础 多态&#xff08;Polymorphism&#xff09;是面向对象编程中的一个重要概念&#xff0c;它允许使用统一的接口来表示不同的对象和操作。多态性有两种主要形式&#xff1a;静态多态性&#xff08;编译时多态性&#xff09;和动态多…

【设备树添加节点】

节点结束位置都需要加分号 of_iomap 完成映射 of_property_read_u32_array of_property_read_string of_fine_node_by_path

子虔科技出席2023WAIC“智能制造融合创新论坛”

7月7日&#xff0c;2023世界人工智能大会&#xff08;WAIC&#xff09;闵行会场在大零号湾举办。子虔科技联合创始人周洋作为专家嘉宾受邀参与智能制造融合创新论坛大会。会上探讨了工业和制造业数字化转型的机遇、挑战和对策。其中&#xff0c;周洋提到&#xff0c;工业制造业…

【电路笔记】-电源电压

电源电压 文章目录 电源电压1、概述1.1 交流发电机1.2 电池1.3 理想电压源1.4 实际电压源1.5 连接规则 2、相关源2.1 压控电压源 (VCVS)2.2 电流控制电压源 (CCVS) 3、总结 在本文中&#xff0c;我们详细介绍了称为电源电压的重要电子元件的架构、功能和使用。 我们首先提出理想…

leetcode刷题详解——粉刷房子

1. 题目链接&#xff1a;LCR 091. 粉刷房子 2. 题目描述&#xff1a; 假如有一排房子&#xff0c;共 n 个&#xff0c;每个房子可以被粉刷成红色、蓝色或者绿色这三种颜色中的一种&#xff0c;你需要粉刷所有的房子并且使其相邻的两个房子颜色不能相同。 当然&#xff0c;因为…