1.背景

2.源码解读

调用该方法的地方

方法源码解读

    /**
* 取消获取资源(异常处理时都需要用到)
* 方法主要功能:
* 1.处理当前取消节点的状态;
* 2.将当前取消节点的前置非取消节点和后置非取消节点"链接"起来;
* 3.如果前置节点释放了锁,那么当前取消节点承担起后续节点的唤醒职责。
*
* @param node
*/
private void cancelAcquire(Node node) {
// 如果节点为空直接返回
if (node == null)
return;
// 处理当前取消节点的状态;
node.thread = null;
node.waitStatus = Node.CANCELLED;
/*
*这段代码用来找到前置的非取消节点
*/
Node pred = node.prev;
while (pred.waitStatus > 0) { // pred.waitStatus > 0 说明当前节点的前置节点已取消,应该继续向前找
pred = pred.prev;
node.prev = pred;
}
/*
*这里注意一下:
* 1.pred是一个实际有效的前节点,即前置的非取消节点
* 2.pred.next 并不一定是 node节点,因为 while (pred.waitStatus > 0)前节点会向前移动
*/
Node predNext = pred.next;
// 修改尾指针:compareAndSetTail(node, pred),指向前置的第一个非取消节点;
if (node == tail && compareAndSetTail(node, pred)) {
// 将新的尾节点的next指针置空:compareAndSetNext(pred, predNext, null);
// 为什么呢? 因为node==tail,说明node节点的前一个非取消节点就是就会说最后一个节点,即无下一个节点
compareAndSetNext(pred, predNext, null);
} else {
int ws;
// compareAndSetWaitStatus(pred, ws, Node.SIGNAL) 把前置节点设置为下一个获取锁的节点
/*
* 这个if的作用是:让pred节点作为下一个可以获取锁的节点
* 1.pred != head && pred.thread != null
* 2. pred.waitStatus=-1 获取 compareAndSetWaitStatus(pred, ws, Node.SIGNAL
*/
if (pred != head &&
((ws = pred.waitStatus) == Node.SIGNAL ||
(ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
pred.thread != null) {
Node next = node.next;
if (next != null && next.waitStatus <= 0)
// 设置前置节点的下一个节点为:当前节点的下一个节点
compareAndSetNext(pred, predNext, next);
} else {
// 到这里说明 pred是头结点,唤醒node的下一个节点
unparkSuccessor(node);
}
node.next = node; // help GC
}
}

重点逻辑图解说明

 while (pred.waitStatus > 0)
node.prev = pred = pred.prev;

假设当前队列如下图:

当前节点为 t4前一个节点t3的waitStatus=1>0,会继续向前找,最后会找到t2为非取消的前置节点,循环结束

如果当节点是尾节点

        /*
*这里注意一下:
* 1.pred是一个实际有效的前节点,即前置的非取消节点
* 2.pred.next 并不一定是 node节点,因为 while (pred.waitStatus > 0)前节点会向前移动
*/
Node predNext = pred.next;
// 修改尾指针:compareAndSetTail(node, pred),指向前置的第一个非取消节点;
if (node == tail && compareAndSetTail(node, pred)) {
// 将新的尾节点的next指针置空:compareAndSetNext(pred, predNext, null);
// 为什么呢? 因为node==tail,说明node节点的前一个非取消节点就是就会说最后一个节点,即无下一个节点
compareAndSetNext(pred, predNext, null);
// 方法执行结束
}

代码执行队列图:

当前节点不是尾节点的情况

 int ws;
// compareAndSetWaitStatus(pred, ws, Node.SIGNAL) 把前置节点设置为下一个获取锁的节点
/*
* 这个if的作用是:让pred节点作为下一个可以获取锁的节点
* 1.pred != head && pred.thread != null
* 2. pred.waitStatus=-1 获取 compareAndSetWaitStatus(pred, ws, Node.SIGNAL
*/
if (pred != head &&
((ws = pred.waitStatus) == Node.SIGNAL ||
(ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
pred.thread != null) {
Node next = node.next;
if (next != null && next.waitStatus <= 0)
// 设置前置节点的下一个节点为:当前节点的下一个节点
compareAndSetNext(pred, predNext, next);
} else {
// 到这里说明 pred是头结点,唤醒node的下一个节点
unparkSuccessor(node);
}
node.next = node; // help GC

执行示意图:

如果待取消的是t1节点,且t2节点是取消节点,则会直接执行unparkSuccessor(node);,这个过程会从tail指针开始从后往前,找到最靠近头的有效(非取消)节点,唤醒这个线程。

完美!

AQS源码深度解析之cancelAcquire方法解读的更多相关文章

  1. 并发编程(十二)—— Java 线程池 实现原理与源码深度解析 之 submit 方法 (二)

    在上一篇<并发编程(十一)—— Java 线程池 实现原理与源码深度解析(一)>中提到了线程池ThreadPoolExecutor的原理以及它的execute方法.这篇文章是接着上一篇文章 ...

  2. spring5 源码深度解析----- AOP目标方法和增强方法的执行(100%理解AOP)

    上一篇博文中我们讲了代理类的生成,这一篇主要讲解剩下的部分,当代理类调用时,目标方法和代理方法是如何执行的,我们还是接着上篇的ReflectiveMethodInvocation类Proceed方法来 ...

  3. 源码深度解析SpringMvc请求运行机制(转)

    源码深度解析SpringMvc请求运行机制 本文依赖的是springmvc4.0.5.RELEASE,通过源码深度解析了解springMvc的请求运行机制.通过源码我们可以知道从客户端发送一个URL请 ...

  4. SpringMVC 源码深度解析&lt;context:component-scan&gt;(扫描和注冊的注解Bean)

    我们在SpringMVC开发项目中,有的用注解和XML配置Bean,这两种都各有自己的优势,数据源配置比較经经常使用XML配置.控制层依赖的service比較经经常使用注解等(在部署时比較不会改变的) ...

  5. mybatis 3.x源码深度解析与最佳实践(最完整原创)

    mybatis 3.x源码深度解析与最佳实践 1 环境准备 1.1 mybatis介绍以及框架源码的学习目标 1.2 本系列源码解析的方式 1.3 环境搭建 1.4 从Hello World开始 2 ...

  6. 并发编程(十五)——定时器 ScheduledThreadPoolExecutor 实现原理与源码深度解析

    在上一篇线程池的文章<并发编程(十一)—— Java 线程池 实现原理与源码深度解析(一)>中从ThreadPoolExecutor源码分析了其运行机制.限于篇幅,留下了Scheduled ...

  7. spring源码深度解析— IOC 之 容器的基本实现

    概述 上一篇我们搭建完Spring源码阅读环境,spring源码深度解析—Spring的整体架构和环境搭建 这篇我们开始真正的阅读Spring的源码,分析spring的源码之前我们先来简单回顾下spr ...

  8. spring源码深度解析— IOC 之 默认标签解析(上)

    概述 接前两篇文章  spring源码深度解析—Spring的整体架构和环境搭建  和  spring源码深度解析— IOC 之 容器的基本实现 本文主要研究Spring标签的解析,Spring的标签 ...

  9. spring源码深度解析— IOC 之 默认标签解析(下)

    在spring源码深度解析— IOC 之 默认标签解析(上)中我们已经完成了从xml配置文件到BeanDefinition的转换,转换后的实例是GenericBeanDefinition的实例.本文主 ...

  10. spring源码深度解析— IOC 之 开启 bean 的加载

    概述 前面我们已经分析了spring对于xml配置文件的解析,将分析的信息组装成 BeanDefinition,并将其保存注册到相应的 BeanDefinitionRegistry 中.至此,Spri ...

随机推荐

  1. Mysql联合索引生效、失效条件

    引言 联合索引又叫复合索引.两个或更多个列上的索引被称作复合索引. 对于复合索引:Mysql从左到右的使用索引中的字段,一个查询可以只使用索引中的一部份,但只能是最左侧部分.例如索引是key inde ...

  2. java.lang.IllegalStateException: Expected BEGIN_OBJECT but was STRING at line 1 column 2 path $

    java.lang.IllegalStateException: Expected BEGIN_OBJECT but was STRING at line 1 column 2 path $ pack ...

  3. 安卓内核编译:关闭"error, forbidden warning"

    安卓内核编译:关闭error, forbidden warning 背景 最近在编译Android kernel时,遇到error, forbidden warning, 导致编译中断,大大降低了de ...

  4. VS2015 、VS2017 MFC输出日志到控制台窗口

    原来使用VS2010建立的项目,安装VS2017后,发现MFC无法通过调试窗口输出printf打印的内容,在CSDN上找到了一个解决方案,使用后恢复打印调试信息功能,推荐如下: https://blo ...

  5. 树莓派4B-PCA9685驱动舵机

    前言 不知道你们有没有遇到过这么一种情况,直接用树莓派的引脚输出PWM控制舵机,舵机是会出现抖动的.就算代码进行一定的时延迟优化还是会有影响的. 现在我们可以使用PCA9685这个模块去驱动舵机. P ...

  6. 解决方案 | Get-AppxPackage : 无法启动服务,原因可能是已被禁用或与其相关联的设备没有启动

    前几天由于需要卸载一些win10自带应用,导致onenote2016无法启动(根本原因:当时可能remove-appxpackage导致某些微软原生应用出现问题),同时今天使用power shell出 ...

  7. ArchSummit回顾:从云原生到实时数据湖,架构如何支撑业务发展

    [点击了解更多网易热点] 数字化.自动化.智能化的主旋律下,架构的进化也在提速.在近日举办的ArchSummit全球架构师峰会上,网易数帆高级技术专家.资深架构师裴斐和网易数帆高级技术专家周劲松分别分 ...

  8. LeViT:Facebook提出推理优化的混合ViT主干网络 | ICCV 2021

    论文提出了用于快速图像分类推理的混合神经网络LeVIT,在不同的硬件平台上进行不同的效率衡量标准的测试.总体而言,LeViT在速度/准确性权衡方面明显优于现有的卷积神经网络和ViT,比如在80%的Im ...

  9. SMU Summer 2024 Contest Round 2 (7.9)zhaosang

    A-A http://162.14.124.219/contest/1006/problem/A 考查用vector画图 我枚举到n==5才开始用,浪费40分钟,还是找规律太慢,得多学 做题代码如下: ...

  10. Kubernetes 存储概念之Volumes介绍

    Volumes 默认情况下容器中的磁盘文件是非持久化的,对于运行在容器中的应用来说面临两个问题,第一:当容器挂掉,K8S重启它时,文件将会丢失:第二:当Pod中同时运行多个容器,容器之间需要共享文件时 ...