ReentrantLock原理剖析

前言

本文主要讲解底层逻辑,基本不会贴代码,目的是让大家能够真正的知晓原理,对照着逻辑去理解代码看代码也会很快就能看懂。
在讲ReentrantLock原理之前,我们先回顾下ReentrantLock的基本用法。ReentrantLock是一个锁编程api,让我们达到类似syncronized类似的效果,但它可以提供更多的功能,例如公平锁等。通常的写法如下所示:

class X {    
private final ReentrantLock lock = new ReentrantLock();   

public void m() {     
    doSomething();
    
    // 需要加锁的代码块 
	lock.lock();  // 加锁   
	
	try {        
		// 业务处理
	} finally {        
		lock.unlock() // 释放锁,一定要放在finalyy块中
		}    
	}  
}

ReentrantLock的重点

公平和非公平区别在哪里?

理解ReentrantLock一切的起点从暴露出来的接口开始。接下来,我们探索的入口就是从这个lock()方法开始:
在这里插入图片描述
内部调用的是sync.lock(),我们经常讲的ReentrantLock有公平锁和非公平锁两种实现,这里的sync就是非公平同步器和公平同步器之一。为了高性能,ReentrantLock 默认采用了非公平锁实现方式。相关类的继承关系如下所示:
在这里插入图片描述
RentrantLock实际上是一层包装,根据公平和非公平需要,持有相应的公平或者非公平的同步器。内部基本所有的功能都是通过Sync及其子类实现的,Sync的lock方法是一个抽象方法,实现是通过子类实现的,公平锁和非公平锁lock操作的流程如下所示:
在这里插入图片描述
非公平锁加锁流程会先有一个cas的操作尝试获取锁,成功了就执行业务代码块,不用再去排队。如果cas失败,就走正常的尝试获取锁、入队流程。因为少了入队而产生的阻塞、唤醒,使得非公平锁的性能很好。这样也会带来一个问题,就是如果新来的线程总是能抢到锁的话,队列里的线程就因为老是抢不到锁而处于饥饿状态。
在这里插入图片描述
公平锁加锁流程就比较简单了,直接进行尝试获取锁、入队流程。这里的尝试获取锁里面有特殊判断(接下来马上会讲),不会造成非公平的现象,可以理解不管谁来都老老实实的去排队。
公平锁和非公平锁在尝试获取锁的操作上面会有一丝不同。如果是非公平锁,会直接通过cas再次尝试加锁(够无赖的)。而公平锁会首先判断队列里面是否已经有等待的线程了,如果有则老老实实去排队,如果没有,那就各凭本事去抢锁了,很公平。两个tryAcquire的流程如下所示:
在这里插入图片描述
在这里插入图片描述
小结:非公平锁加锁流程会先有一个cas的操作尝试获取锁,成功了就执行业务代码块,不用再去排队。如果cas失败,就走正常的尝试获取锁、入队流程。公平锁加锁会判断是否有线程在队列中,如果没有则直接进行尝试获取锁,成功了就执行业务代码块,失败则开始入队。如果有线程已经在队列中,则进行入队。

可重入的实现方式

可重入性就是一个线程不用释放,可以重复的获取一个锁n次,只是在释放的时候,也需要相应的释放n次。可重入锁的实现解决了同一个线程需要多次获取同一个锁的问题。
要实现可重入锁,至少需要保存两个东西:现在持有锁的线程已经上锁的次数。刚才讲上锁流程的时候,我们已经提到过两个对象exclusiveOwnerThread和state。exclusiveOwnerThread保存了持有锁的线程,state在>0的情况下保存了线程对锁的重入次数,这样在释放的时候减去相应的次数即可,等state=0时就说明已经完全释放掉了,这时候再将exclusiveOwnerThread设置为null。
在这里插入图片描述
到此为止属于ReentrantLock的内容实际上基本就结束了,剩下的部分全部是AQS实现的,所以我们的重点要转向去研究AQS的结构和流程。

AQS

AQS的结构

AQS的结构不难,我们要做到的就是把下面这个图能自己闭着眼睛画出来。
在这里插入图片描述
我们上面讲的获取锁,释放锁,操作的就是这个state对象。state的含义并非固定的,在ReentrantLock中,state初始化的时候是0,表示没有上锁,=1表示有线程对它加了锁,>1表示重入了多少次。在CountDownLatch中,它表示有多少个任务需要等待完成。ReentrantLock中state状态流转如下所示:
在这里插入图片描述
AQS的所有结点都是Node对象,Node对象结构在上面的类图里面已经画出来了,我们需要初步的理解它:
pre/next:没什么好说的,指向前驱、后驱的结点指针。
thread:保存了入队列的线程。
waitStatus:重点理解对象!!waitStatus包括CANCELLED(线程已经被删除了)、SIGNAL(后面的线程需要被唤醒)、CONDITION(线程在等待condition条件)、PROPAGATE(共享锁需要传递),先了解即可。
nextWaiter:指向下个等待条件的Node对象,如果不是Condition的对象,默认是Node.EXCLUSIVE,表示这是个排他锁。

在有线程获取锁失败入队的情况下,整个AQS的结构大概就是这个样子:
在这里插入图片描述

入队流程

以非公平锁为例,尝试加锁失败后会进入到AQS队列中。队列的对象都是Node对象,所以加入队列前会根据这个线程创建一个Node对象,Node结点的nextWaiter设置成了Node.EXCLUSIVE,然后将该结点入队,流程如下所示:
在这里插入图片描述
对应代码为private Node addWaiter(Node mode)。

队列中的线程怎么抢锁

线程入队了,那么队列中的线程怎么抢锁呢?看起来似乎很简单,既然都在队列中了,那肯定就是队列头的线程先拿到锁呗。但是我们需要考虑几个问题:
1、队列中的线程抢锁什么时候触发?
2、我们知道线程没有抢到锁会被阻塞,轮到它时,该怎么被唤醒?
3、队列中如果有无效的线程怎么办?
我们带着这几个问题去看看队列的处理。
实际上,在线程抢锁失败后包装成一个Node而入队,在入队完成后,紧接着就会触发队列抢锁操作,如下所示:
在这里插入图片描述
现在我们知道队列的中的线程抢锁,实际上是从入队的这个线程触发的,整个处理的流程被包在了一个死循环里面,具体的流程如下所示:
在这里插入图片描述
在初始化队列的时候,我们设置的了head,tail,如果入队的这个线程是第一个线程,那么它的前驱结点刚好就是head,说明它在队列头,该它去抢锁了。如果不是,理论上该线程就应马上被阻塞,但是阻塞前会做一些额外判断工作,这个时候我们的waitStatus就排上用场了!

waitStatus

waitStatus包括CANCELLED(1:线程已经被删除了)、SIGNAL(-1:后面的线程需要被唤醒)、CONDITION(-2:线程在等待condition条件)、PROPAGATE(-3:共享锁需要传递),状态流转如下所示:
在这里插入图片描述
Condition和Propogate两种状态分别用于lock.newConditon和共享锁,这里暂不涉及。阻塞前的waitStatus判断和处理如下所示:
在这里插入图片描述
当需要被阻塞的线程设置好了“哨兵”后,就放心的调用LockSupport.park方法阻塞自己。那么B什么时候会被唤醒呢?当A线程业务处理完成后,调用unlock方法释放琐,里面就会唤醒后续的线程,流程如下所示:
在这里插入图片描述
释放锁跟加锁差不多的操作,重点就在这个唤醒上面,唤醒的流程如下所示:
在这里插入图片描述
因为头结点是一个dummy结点,现在要唤醒它后面的线程,waitStatus需要重新恢复成0的状态。然后拿到后继线程结点,这个拿很有讲究,首先是通过h.next尝试获取,然后看看是否能拿到。如果能拿到而且是正常阻塞的线程,那万事大吉直接唤醒它。如果不幸是空或者已经删除了,则从tail往前找啊找,找到最前面的一个正常结点(没有破坏FIFO的性质,waitStatus < 0为正常状态,这里情况就是SIGNAL。队列中如果有无效的线程就会被跳过),然后唤醒它。关于为什么采用tail往前遍历的做法,可以参考这个:https://www.zhihu.com/question/50724462?sort=created。
唤醒线程后,线程就继续在死循环里去抢锁了。
对应的代码在final boolean acquireQueued(final Node node, int arg)中。

condition怎么玩的?

condition用于线程同步编程,实际上它拥有自己的队列,限于篇幅这个放在另外的文章里。

总结

ReentrantLock是最常用的锁API,在线程池ThreadPoolExecutor、阻塞队列LinkedBlockingQueue,同步工具CyclicBarrier等中都能看到它的身影。掌握好ReentrantLock的原理,可以让我们更加清楚基于它的实现的其他工具类的实现原理。

ReentrantLock实现了类似Syncronized的同步能力,但它还支持了公平锁的能力。根据公平和非公平需要,ReentrantLock持有相应的公平或者非公平的同步器。非公平锁加锁流程会先有一个cas的操作尝试获取锁,成功了就执行业务代码块,不用再去排队。如果cas失败,就走正常的尝试获取锁、入队流程。公平锁尝试加锁会判断是否有线程在队列中,如果没有则直接进行尝试获取锁,成功了就执行业务代码块,失败则开始入队。如果有线程已经在队列中,则进行入队操作。NonfairSync和FairSync的区别体现在lock和tryAcquire两个方法上面。

ReentrantLock通过exclusiveOwnerThread和state来实现可重入,exclusiveOwnerThread保存了持有锁的线程,state在>0的情况下保存了线程对锁的重入次数,这样在释放的时候减去相应的次数即可,等state=0时就说明已经完全释放掉了,这时候exclusiveOwnerThread设置为null。

AQS需要对队列和Node的类结构铭记于心,尤其是state和waitStatus含义,理解了它俩,上锁和释放锁流程就会变得非常容易。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

考研机试刷题第二天:任意进制转任意进制【高进度短除法】

理一下思路&#xff1a; 看了y总的视频之后我觉得这道题其实只需要对上次写的进制转换微微做一下调整即可。 于是我写出了下面的代码 #include <iostream> #include <vector> #include <algorithm> #include <cstring>using namespace std;vector<…

Moonbeam操作指南|如何使用Gelato创建自动化任务

Gelato是一个Web3去中心化自动化网络&#xff0c;允许开发者横跨多个基于EVM兼容区块链上自动化和连接任意的智能合约执行。&#x1f4d1;阅读中文版详细操作教程 举例来说&#xff0c;我们将使用MetaMask作为钱包。同时&#xff0c;您的钱包余额中需要有一些GLMR用于支付自动…

基于海洋捕食者算法的极限学习机(ELM)回归预测-附代码

基于海洋捕食者算法的极限学习机(ELM)回归预测 文章目录 基于海洋捕食者算法的极限学习机(ELM)回归预测1.极限学习机原理概述2.ELM学习算法3.回归问题数据处理4.基于海洋捕食者算法优化的ELM5.测试结果6.参考文献7.Matlab代码 摘要&#xff1a;本文利用海洋捕食者算法对极限学习…

深度学习笔记--本地部署Mini-GPT4

目录 1--前言 2--配置环境依赖 3--下载权重 4--生成 Vicuna 权重 5--测试 6--可能出现的问题 1--前言 本机环境&#xff1a; System: Ubuntu 18.04 GPU: Tesla V100 (32G) CUDA: 10.0 项目地址&#xff1a;https://github.com/Vision-CAIR/MiniGPT-4 2--配置环境依赖 …

python面试题

文章目录 赋值、深拷贝和浅拷贝有什么区别&#xff1f;元组和列表有什么不同&#xff1f;和is有什么不同&#xff1f;集合怎么转字典&#xff1f;字典怎么遍历&#xff1f;如何在Python中实现多线程&#xff1f;如何实现tuple和list的转换&#xff1f;实现删除一个list里面的重…

智能无人蜂群作战系统适应性进化模型仿真研究

源自&#xff1a;系统仿真学报 作者&#xff1a;李志强, 李元龙, 殷来祥, 马向平 摘 要 智能无人蜂群作战系统主要由有限行为能力的大规模作战个体组成&#xff0c;一般不具备应对复杂战场环境和作战对手变化的适应能力。采用遗传算法与增强学习相结合的方法探索构建基于个体…

Tre靶场通关过程(linpeas使用+启动项编辑器提权)

Tre靶场通关 通过信息收集获得到了普通用户账号密码&#xff0c;利用PEASS-ng的linpeas脚本进行提权的信息收集&#xff0c;根据已有信息进行提权。 靶机下载地址&#xff1a; https://download.vulnhub.com/tre/Tre.zip 信息收集 靶机IP探测&#xff1a;192.168.0.129 a…

vue2实现高德地图 JSAPI 2.0轨迹回放组件(MoveAnimation)

vue2实现高德地图 JSAPI 2.0轨迹回放组件(MoveAnimation) 声明: 本人是做java后端的,组件抽取不是很规范请大家见谅 前提: 需要注册高德开放平台,之后创建应用并且开通Web端(JS API)平台,然后拿到securityJsCode和key 实现效果: 1. 基础抽取 注意: 将securityJsCode和key修改为…

Hystrix线程池问题

背景&#xff1a;在一个以springcloud为基础架构的微服务项目中&#xff0c;活动期间并发量一大就会出现服务调用失败的问题。经定位发现&#xff0c;被调用服务中无对应的请求日志&#xff0c;继续通过日志查询确认是feign调用时出现服务降级&#xff0c;进入降级方法统一返回…

极化码的入门与探索

文章目录 极化码的基础先验知识二进制输入离散无记忆信道模型(Binary-input Discreten Memoryless Channel, B-DMC)二进制离散输入信道的ML判决和错误率B-DMC相关参数的定义和理解 两信道极化N信道极化的解释信道极化分解的蝶形结构补充&#xff1a;生成矩阵的结构 极化码的基础…

【2023 年第十三届 MathorCup 高校数学建模挑战赛】A 题 量子计算机在信用评分卡组合优化中的应用 42页论文及代码

相关信息 &#xff08;1&#xff09;建模思路 【2023 年第十三届 MathorCup 高校数学建模挑战赛】A 题 量子计算机在信用评分卡组合优化中的应用 详细建模过程解析及代码实现 【2023 年第十三届 MathorCup 高校数学建模挑战赛】 B 题 城市轨道交通列车时刻表优化问题 详细建…

C6678学习-EDMA

文章目录 1、简介1. EDMA3概述2、EDMA3的组成3、EDMA3的工作流程4、EDMA3通道控制器&#xff08;EDMA3CC&#xff09;5、触发方式 2、EDMA3的传输1、传输数据块的定义2、传输类型3、参数PaRAM4、通道5、OPT参数 3、补充1、EDMA3通道控制器区域 1、简介 1. EDMA3概述 基于C66x…

idea使用git遇到的小问题

idea使用git遇到的小问题 前置说明颜色含义中文插件修改提交的用户名 前置说明 idea版本为2022专业版 github需要自己会科学上网 颜色含义 在idea中使用github后&#xff0c;会发现项目中会有各种各样的颜色&#xff0c;如图所示文件全为绿色 这颜色含义分别为&#xff1a;…

亚马逊、Lazada、阿里国际、eBay、Temu、Ozon好消息不断,机会来了

1. 亚马逊第一季度营收1273.58亿美元 同比扭亏为盈 亚马逊2023财年第一季度财报。亚马逊第一季度净销售额为1273.58亿美元&#xff0c;与上年同期的1164.44亿美元相比增长9%&#xff0c;不计入汇率变动的影响为同比增长11%&#xff1b;净利润为31.72亿美元&#xff0c;上年同期…

牛客网---CM11 链表分割 代码详解+哨兵位的比较

文章目录 前言CM11 链表分割链接&#xff1a;方法一&#xff1a;尾插(带哨兵位)1.1 思路&#xff1a;1.2 代码&#xff1a;1.3 流程图1.4 注意点 方法二&#xff1a;尾插(不带哨兵位)2.1代码&#xff1a; 对比&#xff1a; 总结 前言 独处未必孤独喜欢就是自由 本章的内容是牛…

xawtv涉及的vivid系统调用分析

xawtv涉及的vivid系统调用分析 文章目录 xawtv涉及的vivid系统调用分析调用过程分析摄像头驱动程序必需的11个ioctl非必须必须 分析数据的获取过程1.请求分配缓冲区: ioctl(4, VIDIOC_REQBUFS // 请求系统分配缓冲区2.查询映射缓冲区:3.把缓冲区放入队列:4.启动摄像头5.用selec…

Shell+VCS学习2

Shell脚本常见问题 rm -f $2~ while read line 【最佳】形如while read line;do echo $line;done <test使用输入重定向的方式则每次只占用一行数据的内存&#xff0c;而且是在当前shell环境下执行的&#xff0c;while内的变量赋值、数组赋值在退出while后仍然有效。 nam…

Jetson Nano emmc版本系统镜像备份和烧录

一、镜像备份 1&#xff0e;将待复制的jetson设备进入恢复模式&#xff0c;用数据线连接jetson设备和主机。 对于原厂开发板将FC_REC引脚与GND短接&#xff0c;通过micro-usb到usb数据线连接到电脑。 在电脑的ubuntu通过lsusb命令查看需要备份的设备是否已经接入&#xff0c…

【VAR | 时间序列】以美国 GDP 和通货膨胀数据为例的VAR模型简单实战(含Python源代码)

以美国 GDP 和通货膨胀数据为例&#xff1a; 1. 数据集 下载数据我们需要从 FRED 数据库下载美国 GDP 和通货膨胀数据&#xff0c;并将它们存储在 CSV 文件中。可以在 FRED 网站&#xff08;https://fred.stlouisfed.org/&#xff09;搜索并下载需要的数据。在这里&#xff0…

Transformer结构细节

一、结构 Transformer 从大的看由 编码器输入、编码器、解码器、解码器输入和解码器输出构成。 编码器中包含了词嵌入信息编码、位置编码、多头注意力、Add&Norm层以及一个全连接层&#xff1b; 解码器中比编码器多了掩码的多头注意力层。 二、模块 2.1 Input Embeddi…