AQS中的cancelAcquire何时会运行

简介: 哈喽,我是子牙。十余年技术生涯,一路披荆斩棘从技术小白到技术总监到JVM专家到创业。技术栈如汇编、C语言、C++、Windows内核、Linux内核。特别喜欢研究虚拟机底层实现,对JVM有深入研究。分享的文章偏硬核,很硬的那种。

哈喽,我是子牙。十余年技术生涯,一路披荆斩棘从技术小白到技术总监到JVM专家到创业。技术栈如汇编、C语言、C++、Windows内核、Linux内核。特别喜欢研究虚拟机底层实现,对JVM有深入研究。分享的文章偏硬核,很硬的那种。

手撸过JVM、内存池、垃圾回收算法、synchronized、线程池、NIO、三色标记算法…

erafbbd8u7.png

今天分享一篇JUC包中AQS的一个方法的深度解读,可能你都没有关注过这个细节吧。

这篇文章适合对AQS有一定基础的童鞋学习。学完本篇文章就可以对AQS高级部分有更深入的理解:比如AQS中的节点什么时候会修改自己的waitStatus、Node.CANCELLED状态有何意义、cancelAcquire何时会运行……

尤其是cancelAcquire何时会运行,最近讲完AQS课被问得比较多。今天抽个空写篇文章分享下。cancelAcquire这个方法内部做了什么,后面写文章分享,这个方法的代码也很难理解。

国际惯例,先上代码。

image.png

这两段代码是AQS的源码,因为AQS被很多类共用,本篇文章是从ReentrantLock角度进行分析。

分析问题

当failed为true时才会执行方法cancelAcquire,那什么情况下failed为true呢?try代码段执行过程中出现异常。

那什么情况下try代码段执行过程中会出现异常呢?整个流程看下来,只有两个地方有可能:predecessor、tryAcquire。看下源码,都会抛出异常。

image.png

那真正导致try代码段执行过程中出现异常是因为哪一个呢?我们来逐个分析下。

如果是因为tryAcquire

肯定不可能。为什么这么说呢?看代码

image.png

如果tryAcquire没有被重写,程序根本执行不到方法acquireQueued。因为不会有线程拿到锁,不会有线程因抢不到锁入队列。

如果是因为predecessor

其实也不可能。虽然这里有抛出异常的代码,但是这段代码永远不会执行到。所以注释里有这句话。

The null check could be elided, but is present to help the VM
空检查可以省略,但存在是为了帮助VM

为什么说抛出异常那段代码永远不会执行到呢?方法predecessor是入队后执行的,AQS队列有这样的特点:入队后至少有两个节点,第一个节点永远是占有锁的线程对应的节点。

第三种可能

刚刚分析完我们好不容易找到的两条线索,经过缜密的分析,得出结论都不可能。于是有了第三种可能:第二个节点加锁失败,没有机会将failed改为true。有没有可能呢?来分析下

image.png

先回答第一个问题:什么时候第二个节点加锁失败呢?答案是非公平锁的时候。即占有锁的线程刚释放完锁,刚唤醒第二个节点或者,这时候恰好来了一个线程拿到了锁,这时候唤醒的第二个节点来抢锁就会抢锁失败。

虽然抢锁失败了,但是也不会执行到finally代码段,而是重新自旋设置闹钟,然后调用park阻塞自己等待再次唤醒。

结论一

你还能想到其他可能吗?我是想不到了。那cancelAcquire永远不会执行吗?

在ReentrantLock中,不管是公平锁还是非公平锁,cancelAcquire都不会运行。那道格李为什么这样写呢?这个有点像编译器编译sync时会生成两个monitorenter,一种保险策略吧。

image.png

还有一种可能:有可能是保持代码统一,反正用到的时候会用到,用不到的时候也不会被执行到。有的童鞋可能说,道格李这样的大神不可能犯这种低级错误吧。我倒不觉得这是一种低级错误,保持统一反映了代码洁癖、反映了先有思想后有代码。

结论二

那什么情况下方法cancelAcquire会运行呢?响应中断的程序中,句式如

image.png

我是子牙老师,喜欢钻研底层,深入研究Windows、Linux内核、JVM。如果你也喜欢研究底层,欢迎关注我的公众号【硬核子牙】

相关文章
|
12月前
|
安全 Java 程序员
Java多线程(1)---多线程认识、四种创建方式以及线程状态
Java多线程(1)---多线程认识、四种创建方式以及线程状态
100 0
|
3月前
|
消息中间件 设计模式 安全
深入理解AQS队列同步器原理-从ReentrantLock的非公平独占锁实现来看AQS的原理
深入理解AQS队列同步器原理-从ReentrantLock的非公平独占锁实现来看AQS的原理
|
3月前
|
SQL 安全 Java
JUC多线程-线程池-Thredalocal-CAS-AQS-死锁
JUC多线程-线程池-Thredalocal-CAS-AQS-死锁
|
4月前
|
存储 设计模式 算法
队列同步器AQS-AbstractQueuedSynchronizer 原理分析
队列同步器AQS-AbstractQueuedSynchronizer 原理分析
78 0
|
Java 容器
并发编程-16AQS同步组件之CountDownLatch 闭锁
并发编程-16AQS同步组件之CountDownLatch 闭锁
68 0
并发编程-18AQS同步组件之 CyclicBarrier 同步屏障
并发编程-18AQS同步组件之 CyclicBarrier 同步屏障
61 0
|
Java 调度 容器
并发编程-15并发容器(J.U.C)核心 AbstractQueuedSynchronizer 抽象队列同步器AQS介绍
并发编程-15并发容器(J.U.C)核心 AbstractQueuedSynchronizer 抽象队列同步器AQS介绍
109 0
|
监控 并行计算 Java
《JUC并发编程 - 高级篇》01 - 进程与线程概述 | 02 - Java线程(创建线程、查看线程、线程常见方法、线程状态)(四)
《JUC并发编程 - 高级篇》01 - 进程与线程概述 | 02 - Java线程(创建线程、查看线程、线程常见方法、线程状态)
《JUC并发编程 - 高级篇》01 - 进程与线程概述 | 02 - Java线程(创建线程、查看线程、线程常见方法、线程状态)(四)
|
存储 Java 应用服务中间件
《JUC并发编程 - 高级篇》01 - 进程与线程概述 | 02 - Java线程(创建线程、查看线程、线程常见方法、线程状态)(一)
《JUC并发编程 - 高级篇》01 - 进程与线程概述 | 02 - Java线程(创建线程、查看线程、线程常见方法、线程状态)
《JUC并发编程 - 高级篇》01 - 进程与线程概述 | 02 - Java线程(创建线程、查看线程、线程常见方法、线程状态)(一)
|
监控 Java Linux
《JUC并发编程 - 高级篇》01 - 进程与线程概述 | 02 - Java线程(创建线程、查看线程、线程常见方法、线程状态)(二)
《JUC并发编程 - 高级篇》01 - 进程与线程概述 | 02 - Java线程(创建线程、查看线程、线程常见方法、线程状态)
《JUC并发编程 - 高级篇》01 - 进程与线程概述 | 02 - Java线程(创建线程、查看线程、线程常见方法、线程状态)(二)