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

上篇文章介绍了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.mzph.cn/news/157612.shtml

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

相关文章

Spring Cloud实战 |分布式系统的流量控制、熔断降级组件Sentinel如何使用

专栏集锦&#xff0c;大佬们可以收藏以备不时之需 Spring Cloud实战专栏&#xff1a;https://blog.csdn.net/superdangbo/category_9270827.html Python 实战专栏&#xff1a;https://blog.csdn.net/superdangbo/category_9271194.html Logback 详解专栏&#xff1a;https:/…

数据的4个等级

除了可以将数据分为定量和定性的&#xff0c;数据还可以分为以下4个等级&#xff0c;每个等级都有不同的控制和数学操作等级&#xff1b; 定类等级&#xff08;nominal level&#xff09; 定序等级&#xff08;ordinal level&#xff09; 定距等级&#xff08;interval level&a…

【CVPR 2023】解读VideoFusion:基于噪声共享机制的视频生成

Diffusion Models视频生成-博客汇总 前言:达摩院开源的VideoFusion是为数不多同时开源模型和推理代码的视频生成工作,通过设计噪声分解机制有效提高视频的时空连贯性,在一些关键指标上远超GAN-based方法和2022年谷歌的VDM。更重要的是,Diffusers库以此为基础,写了关键的两…

同时创建多个websoket(初始化多个连接、断开的重连、每个连接定时发消息、每个连接存储接收的数据(vuex或者pinia))

可复制现成代码直接使用&#xff01;&#xff01; 1.下边的例子演示了创建10个WebSocket 实例&#xff0c;当其中某一个连接失败时&#xff0c;会自动进行重连 <template><div></div> </template><script setup> import { ref, reactive, onMo…

ssh和scp的基本使用

ssh和scp的基本使用 1&#xff0c;ssh 本地连接远程服务器 ssh userhostname第一次连接时输入密码会生成密钥&#xff0c;后续就可以直接连接了 ssh配置文件&#xff1a;/etc/ssh/sshd_config 2&#xff0c;scp 传输本地文件至远程服务器 命令格式 scp [参数] [原路径] […

求二叉树的最大密度(可运行)

最大密度&#xff1a;二叉树节点数值的最大值 如果没有输出结果&#xff0c;一定是建树错误&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01; 我设置输入的是字符型数据&#xff0c;比较的ASCII值。 输入&#xff1a;FBE###CE### 输…

基于单片机设计的气压与海拔高度检测计(采用MPL3115A2芯片实现)

一、前言 随着科技的不断发展&#xff0c;在许多领域中&#xff0c;对气压与海拔高度的测量变得越来越重要。例如&#xff0c;对于航空和航天工业、气象预报、气候研究等领域&#xff0c;都需要高精度、可靠的气压与海拔高度检测装置。针对这一需求&#xff0c;基于单片机设计…

19.删除链表的倒数第 N 个节点

​题目来源&#xff1a; leetcode题目&#xff0c;网址&#xff1a;19. 删除链表的倒数第 N 个结点 - 力扣&#xff08;LeetCode&#xff09; 解题思路&#xff1a; 使用双指针找到倒数第 N1 个节点后删除链表的第 N 个节点即可。注意当 N 为链表长度时&#xff0c;倒数第 N1 …

Google Play 搜索不到应用

Google Play搜索不到已上架应用 这可能是由于多种原因造成的。首先&#xff0c;请确保你的应用在 Google Play 商店上已经成功上架&#xff0c;并且通过了审核。 如果你的应用已经上架&#xff0c;但在搜索时无法找到&#xff0c;可能有以下一些原因&#xff1a; 「1.索引延迟…

wpf devexpress实现输入验证使用验证规则

打开此项目 目标是一个registration form行为像google registration form。打开Google registration form 研究它的行为。当form是第一次显示&#xff0c;它的“Register”按钮应该启动&#xff1b;编辑器没有提示任何输入错误。输入First Name编辑器字段&#xff0c;清理输入…

端到端数据保护浅析

作为最重要的数据保护方式之一&#xff0c;NVMe端到端数据保护被众多企业用户所看重&#xff0c;它可以有效降低静默错误的发生&#xff0c;保护范围涵盖数据自Host端生成直至写入SSD NAND当中&#xff0c;以及从SSD NAND读取直至返回Host的全部流程。它使得数据不论是在SSD内部…

服务器安全怎么保障,主机安全软件提供一站式保护

服务器主机安全是指保护服务器主机免受未经授权的访问、破坏、窃取或滥用。 现在如今大部分公司、单位的相关数据都是存储在云端服务器上&#xff0c;这样即方便查询也方便保存。 可是一旦服务器主机受到威胁&#xff0c;损失将会不可估计。 以下是一些服务器主机安全的建议…

支付宝生僻字选择器

本文的数据来源于支付宝网页版本生僻字选择器。 let rareWords[{spell: "a",words: ["奡", "靉", "叆"]}, {spell: "b",words: ["仌", "昺", "竝", "霦", "犇", "愊…

粒子系统three.js

Three.js是一个非常流行的JavaScript 3D库&#xff0c;它提供了许多强大的功能来创建各种3D场景和动画效果。其中粒子系统是Three.js中非常重要的一部分&#xff0c;它可以用于创建各种特效&#xff0c;如火焰、烟雾、雨雪等等。本文将详细讲解Three.js中粒子系统的使用。 1. …

MySQL数据库——存储过程-条件处理程序(通过SQLSTATE指定具体的状态码,通过SQLSTATE的代码简写方式 NOT FOUND)

目录 介绍 案例 通过SQLSTATE指定具体的状态码 通过SQLSTATE的代码简写方式 NOT FOUND 介绍 条件处理程序&#xff08;Handler&#xff09;可以用来定义在流程控制结构执行过程中遇到问题时相应的处理步骤。具体语法为&#xff1a; DECLARE handler_action HANDLER FOR c…

Linux调度域与调度组

引入调度域的讨论可以参考这篇文章。这篇笔记重点分析了内核调度域相关的数据结构以及内核用于构建调度域的代码实现&#xff0c;以此来加深对调度域的理解。调度域是调度器进行负载均衡的基础。 调度域拓扑层级 整个系统的调度域组成一个层级结构&#xff0c;内核设计了stru…

上海亚商投顾:沪指冲高回落 短剧、地产股集体走强

上海亚商投顾前言&#xff1a;无惧大盘涨跌&#xff0c;解密龙虎榜资金&#xff0c;跟踪一线游资和机构资金动向&#xff0c;识别短期热点和强势个股。 一.市场情绪 三大指数早盘冲高&#xff0c;创业板指盘初涨超1%&#xff0c;午后则集体下行翻绿&#xff0c;北证50一度大涨…

MyBatis:关联查询

MyBatis 前言关联查询附懒加载对象为集合时的关联查询 前言 在 MyBatis&#xff1a;配置文件 文章中&#xff0c;最后介绍了可以使用 select 标签的 resultMap 属性实现关联查询&#xff0c;下面简单示例 关联查询 首先&#xff0c;先创建 association_role 和 association_…

【nacos】Java调用nacos SDK获取配置信息为null

通过 Nacos 提供的 Java 客户端 SDK 来获取配置信息&#xff0c;但是结果是null <!--添加maven依赖 --> <dependency><groupId>com.alibaba.nacos</groupId><artifactId>nacos-client</artifactId><version>2.1.2</version> …

【华为OD机试python】告警抑制【2023 B卷|100分】

【华为OD机试】-真题 !!点这里!! 【华为OD机试】真题考点分类 !!点这里 !! 题目描述 告警抑制,是指高优先级告警抑制低优先级告警的规则。高优先级告警产生后, 低优先级告警不再产生。请根据原始告警列表和告警抑制关系,给出实际产生的告警列表。 不会出现循环抑制的情况…