ReentrantLock 源码解读
创始人
2024-05-30 21:48:14
0

一、ReentrantLock

ReentrantLockjava JUC 中的一个可重入锁,在上篇文章讲解 AQS 源码的时候提到 ReentrantLock 锁是基于 AQS 实现的,那是如何使用的 AQS 呢,本篇文章一起带大家看下 ReentrantLock 的源码。

AQS 中,如果需要使用AQS的特征则需要子类根据使用的场景,重写下面方法,

//查询是否正在独占资源,condition会使用
boolean isHeldExclusively()	
//独占模式,尝试获取资源,成功则返回true,失败则返回false
boolean tryAcquire(int arg)
//独占模式,尝试释放资源,成功则返回true,失败则返回false
boolean tryRelease(int arg)
//共享模式,尝试获取资源,如果返回负数表示失败,否则表示成功。
int tryAcquireShared(int arg)
//共享模式,尝试释放资源,成功则返回true,失败则返回false。
boolean tryReleaseShared(int arg)

由于这里 ReentrantLock 锁的特性,所以下面我们只需关注独占模式下的几个方法即可。

说明:本文中关于 AQS 中的方法没有做过多的解释,不了解的小伙伴可以看下这篇对 AQS 源码分析的文章,和当前文章在同一专栏:

https://blog.csdn.net/qq_43692950/article/details/129367736

下面一起开始 ReentrantLock 源码的分析:

二、ReentrantLock 的 Sync、FairSync、NonfairSync

2.1 Sync、FairSync、NonfairSync

在声明 ReentrantLock 锁时,有两种方式,一种是无参构造函数,一种则需要指定一个 fair 参数:

new ReentrantLock();
new ReentrantLock(false);

当使用无参构造函数声明时,则是创建了一个 NonfairSync 对象:

在这里插入图片描述

通过有参的构造函数,则根据传入的 fair 可以选择创建一个 FairSync 对象:

在这里插入图片描述

其实这里也不难理解 NonfairSyncFairSync 其实就是ReentrantLock 锁中的非公平锁和公平锁两种类型。

点到这两个类中,可以看到都继承自 Sync 类:

在这里插入图片描述
在这里插入图片描述

Sync 类,则继承了 AQS

在这里插入图片描述

到这里了,我们寻找几个关键的方法,在AQS中独占模式下,两大关键的方法是交由子类进行实现的,分别是 tryAcquire 尝试获取资源,和 tryRelease 尝试释放资源。

首先来看 tryAcquire 尝试获取资源:

通过 Sync 类的实现源码发现并没有重写 tryAcquire 方法,那该方法肯定在下面的子类FairSyncNonfairSync ,分别看下源码确实存在重写的方法:

在这里插入图片描述
在这里插入图片描述

2.2 NonfairSync 下的 tryAcquire

首先看下 NonfairSynctryAcquire 实现逻辑,可以看到又调用了 nonfairTryAcquire 就是 Sync 类中的 nonfairTryAcquire ,从命名上可以分析出就是非公平锁的尝试获取资源,直观就是非公平锁下获取锁操作:

在这里插入图片描述
进入到 Sync 类中的 nonfairTryAcquire中,可以看到首先获取到 AQS 中的共享资源 state,如果 state 等于 0 ,则将 state 的值修改为 acquires(默认为1,下面会分析到),并设置AQS的独占线程为当前线程,并返回 true ,说白了不就是获取到锁了吗,那就可以理解为 state 等于 0 即是无锁的状态,下面将 state 的值修改为 acquires 就是获取到锁了,改变资源的状态:

在这里插入图片描述

接着如果 state 的值不是 0 ,则当前锁已经被别的线程持有了,这里又判断了下,如果持有锁的线程正好是当前的线程,那不就是锁的重入吗,这种情况下可以直接获得锁,不过这里为了记录重入的次数,对 state 共享资源进行了 + acquires 操作,其实就是 +1 操作。

在这里插入图片描述

如果都没有成功,那此时则获取锁失败,返回 false

2.3 FairSync下的 tryAcquire

FairSync 类下的 tryAcquire 方法中,和前面 NonfairSync 类似,但不同的是,在获取到锁时,也就是拿到 state 等于 0 ,进行修改资源时,多了步 hasQueuedPredecessors 的判断:

在这里插入图片描述

下面可以进到 hasQueuedPredecessors 的方法中,可以看到是由 AQS 提供的方法,主要就是判断当前节点线程的前面是否还有等待的线程,因为 FairSync 实现的是公平锁的原则,如果当前线程前面还有等待线程,则获取锁资源也轮不到自个,让前面的老大先来:

在这里插入图片描述
hasQueuedPredecessors 方法理解后,其余的逻辑则和 NonfairSync 中的一致了。

2.4 tryRelease

到这里已经了解到了tryAcquire 尝试获取资源的逻辑,上面提到了两个重要方法,还有一个 tryRelease 没有分析逻辑,还是首先看 Sync 类中是否有重写该方法:

通过源码可以看到,在 Sync 类中就已经对 tryRelease 进行了重写,而 NonfairSyncFairSync 中都没有重写该方法,那释放资源就是走的 Sync 类下的 tryRelease 方法:
在这里插入图片描述

在该方法中,可以看到首先还是获取到了 AQS 中的 state 共享资源,然后对该资源进行 - releases (默认releases1,下面会提到 )操作,其实就是 -1 操作:

在这里插入图片描述
接着判断了下,如果当前线程不是持有锁线程,就抛出异常,也好理解,没有持有锁的线程跑过来释放锁,那肯定有问题了呀。

接着再进行判断 state 是不是等于 0 ,上面讲到在锁重入的情况下,记录重入的次数是对 state 进行 +1 操作,而这边又对 state 进行 -1 操作,如果减到最后 state 有成了最初的 0 ,那不就是重入的锁和当前持有的锁都释放完了吗,这个时候就可以将持有锁的线程置为空了,并修改最新的 state

在这里插入图片描述

看到这里就会发现获取锁和释放锁,无非就是对 AQS 中的共享资源进行操作。理解了这两大核心的方法后,下面就可以看如何运用在 ReentrantLock 中的了。

三、lock.lock()

ReentrantLock 中,需要获取锁时,直接使用 lock.lock() 即可,那 lock.lock() 到底做了什么呢,点到该方法中,可以看到是调用的 Synclock 方法,而 Sync 中的lock 方法是抽象方法,具体实现肯定在子类的 NonfairSync、 FairSync 中。

在这里插入图片描述

3.1 NonfairSync.lock()

首先看点 NonfairSync 非公平锁中的 lock 方法,直接进行了将 AQS 中的共享资源 state0 改为 1 ,如果修改成功,根据上面分析的结论不就是获取锁成功了吗,可以将AQS中的独占线程设为自己了。但是如果其他线程修改成功了,这里使用 CAS 就会修改失败,因此就会进到 acquire 方法,注意这里传递的参数默认就是 1 ,对应着前面括号中的说明:

在这里插入图片描述

acquire 方法,就是 AQS 中的独占模式获取同步资源的逻辑,会调用当前方法的 tryAcquire 尝试获取资源,如果获取不到,则加入到 AQS 的阻塞队列并阻塞挂起线程。

在这里插入图片描述

关于acquire 方法的源码解读可以参考文章开始的链接中对 AQS 源码的解读。

3.2 FairSync.lock()

FairSync 公平锁中,由于需要遵循先进先出的原则,这里没有直接已粗暴的形式对 state 进行修改,而是直接调用了 AQS 中的 acquire 方法,而 acquire 方法又会调用当前类的 tryAcquire 获取资源。

但在当前类的 tryAcquire 方法中,如果获取到了资源,会接着进行判断当前线程的前面是否还有等待的线程,如果有则让出来让别人获取资源,因此就遵循了公平锁的原则,注意这里传递的参数默认就是 1 ,同样对应着前面括号中的说明:

在这里插入图片描述
同样 tryAcquire 尝试获取资源,如果获取不到,则加入到 AQS 的阻塞队列并阻塞挂起线程。

四、lock.unlock()

上面了解到了 lock 的逻辑,既然上锁了肯定需要解锁,下面点到 unlock() 方法中,可以看到直接使用了 Syncrelease 方法释放资源,其实是 AQS 中的 release 方法,注意这里传递的参数默认就是 1 ,同样对应着前面括号中的说明:

在这里插入图片描述

AQSrelease 方法中,首先会调用 Sync 类的 tryRelease 释放资源,然后对已阻塞的线程进行唤醒:

在这里插入图片描述

关于release 方法的源码解读可以参考文章开始的链接中对 AQS 源码的解读。

五、总结

通过阅读 ReentrantLock 的源码可以发现,大量依赖于 AQS 中提供的方法,所以在阅读前一定要理解下 AQS 的作用和功能。

相关内容

热门资讯

122.(leaflet篇)l... 听老人家说:多看美女会长寿 地图之家总目录(订阅之前建议先查看该博客) 文章末尾处提供保证可运行...
育碧GDC2018程序化大世界... 1.传统手动绘制森林的问题 采用手动绘制的方法的话,每次迭代地形都要手动再绘制森林。这...
育碧GDC2018程序化大世界... 1.传统手动绘制森林的问题 采用手动绘制的方法的话,每次迭代地形都要手动再绘制森林。这...
Vue使用pdf-lib为文件... 之前也写过两篇预览pdf的,但是没有加水印,这是链接:Vu...
PyQt5数据库开发1 4.1... 文章目录 前言 步骤/方法 1 使用windows身份登录 2 启用混合登录模式 3 允许远程连接服...
Android studio ... 解决 Android studio 出现“The emulator process for AVD ...
Linux基础命令大全(上) ♥️作者:小刘在C站 ♥️个人主页:小刘主页 ♥️每天分享云计算网络运维...
再谈解决“因为文件包含病毒或潜... 前面出了一篇博文专门来解决“因为文件包含病毒或潜在的垃圾软件”的问题,其中第二种方法有...
南京邮电大学通达学院2023c... 题目展示 一.问题描述 实验题目1 定义一个学生类,其中包括如下内容: (1)私有数据成员 ①年龄 ...
PageObject 六大原则 PageObject六大原则: 1.封装服务的方法 2.不要暴露页面的细节 3.通过r...
【Linux网络编程】01:S... Socket多进程 OVERVIEWSocket多进程1.Server2.Client3.bug&...
数据结构刷题(二十五):122... 1.122. 买卖股票的最佳时机 II思路:贪心。把利润分解为每天为单位的维度,然后收...
浏览器事件循环 事件循环 浏览器的进程模型 何为进程? 程序运行需要有它自己专属的内存空间࿰...
8个免费图片/照片压缩工具帮您... 继续查看一些最好的图像压缩工具,以提升用户体验和存储空间以及网站使用支持。 无数图像压...
计算机二级Python备考(2... 目录  一、选择题 1.在Python语言中: 2.知识点 二、基本操作题 1. j...
端电压 相电压 线电压 记得刚接触矢量控制的时候,拿到板子,就赶紧去测各种波形,结...
如何使用Python检测和识别... 车牌检测与识别技术用途广泛,可以用于道路系统、无票停车场、车辆门禁等。这项技术结合了计...
带环链表详解 目录 一、什么是环形链表 二、判断是否为环形链表 2.1 具体题目 2.2 具体思路 2.3 思路的...
【C语言进阶:刨根究底字符串函... 本节重点内容: 深入理解strcpy函数的使用学会strcpy函数的模拟实现⚡strc...
Django web开发(一)... 文章目录前端开发1.快速开发网站2.标签2.1 编码2.2 title2.3 标题2.4 div和s...