AQS公平锁与非公平锁之源码解析
AQS加锁逻辑
ReentrantLock.lock
public void lock() {sync.acquire(1);}
AbstractQueuedSynchronizer#acquire
public final void acquire(int arg) {if (!tryAcquire(arg) &&acquireQueued(addWaiter(Node.EXCLUSIVE), arg))selfInterrupt();}
addWaiter就是将节点加入队列的尾部,我们先看看非公平锁NonfairSync的tryAcquire
final boolean nonfairTryAcquire(int acquires) {final Thread current = Thread.currentThread();int c = getState();if (c == 0) {if (compareAndSetState(0, acquires)) {setExclusiveOwnerThread(current);return true;}}else if (current == getExclusiveOwnerThread()) {int nextc = c + acquires;if (nextc < 0) // overflowthrow new Error("Maximum lock count exceeded");setState(nextc);return true;}return false;}
- 可以看到是尝试cas获取锁,获取到了将当前线程设置为持有锁的线程
- 在AQS中,有一个STATE变量,当为1时表示该锁被占用,所以cas的是这个status值
- 如果cas失败,就会回到acquire方法,继续调用acquireQueued
公平锁fairSync的tryAcquire
protected final boolean tryAcquire(int acquires) {final Thread current = Thread.currentThread();int c = getState();if (c == 0) {if (!hasQueuedPredecessors() &&compareAndSetState(0, acquires)) {setExclusiveOwnerThread(current);return true;}}else if (current == getExclusiveOwnerThread()) {int nextc = c + acquires;if (nextc < 0)throw new Error("Maximum lock count exceeded");setState(nextc);return true;}return false;}
- 和非公平锁的区别:就是在tryAuquire时会先进行hasQueuedPredecessors,即判断当前是否有节点在队列里,有的话不参与cas竞争,实际上后续的解锁和唤醒操作,对于是否公平都是一样,只有这里体现了公平与非公平的区别,对于公平锁,当解锁唤醒队列中的节点时,此时新的获取锁的请求不会与队列中的节点竞争,保证队列中的节点优先唤醒,即保证了FIFO
AbstractQueuedSynchronizer#acquireQueued
- 再一次调用tryAcquire这个方法,尝试获取锁,获取成功后将该节点设置为头节点,两个结论:1. cas失败两次会进行阻塞,2. 链表中持有锁的节点就是头节点
- 如果失败就会进入shouldParkAfterFailedAcquire:
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {int ws = pred.waitStatus;if (ws == Node.SIGNAL)return true;if (ws > 0) {do {node.prev = pred = pred.prev;} while (pred.waitStatus > 0);pred.next = node;} else {pred.compareAndSetWaitStatus(ws, Node.SIGNAL);}return false;}
- 五个状态:
状态 | 值 | 作用 |
---|---|---|
CANCELLED | 1 | 表示该节点已经取消等待,不再参与锁竞争 |
SIGNAL | -1 | 表示后续节点需要被唤醒 |
CONDITION | -2 | 该节点在等待条件队列中 |
PROPAGATE | -3 | 共享模式下传播信号,让后续线程继续执行 |
默认值 | 0 | 节点刚入队列时的状态,当节点是队尾,状态就是0 |
- 回到shouldParkAfterFailedAcquire方法,当前驱节点状态时SINGAL时,说明前驱节点将锁释放了,将唤醒当前节点(唤醒意味着该节点重新参与竞争锁,因为如果是非公平锁仍需要竞争),当前的线程不应该park,应该回到前面的for循环继续tryacquire
- 如果状态大于0,说明前驱节点已经cancel,这个节点应该移除链表,可以看到这里的while会将其移除链表
- 如果状态此时小于0等于了,说明这个前驱节点是正常的,将其设置为SINGAL状态,意为下次会唤醒当前节点
- parkAndCheckInterrupt,这里就真正进行阻塞了,所以当前驱节点唤醒当前节点时,回到这个位置,重新开始for循环,acquire尝试获取锁
park与sleep的区别:可以被另一个线程调用LockSupport.unpark()方法唤醒;线程的状态和wait调用一样,都是进入WAITING状态
park与wait的区别:wait必须在synchronized里面,且唤醒的是随机,而park是消耗许可,unpark是颁发许可,可以提前unpark,park也会一次性消耗所有许可
总结
我们举一个例子:
ReentrantLock reentrantLock = new ReentrantLock();new Thread(new Runnable() {@Overridepublic void run() {reentrantLock.lock();}}, "Thread-A").start();new Thread(new Runnable() {@Overridepublic void run() {reentrantLock.lock();}},"Thread-B").start();new Thread(new Runnable() {@Overridepublic void run() {reentrantLock.lock();}},"Thread-C").start();
- 以上是三个线程尝试加锁,当然是只有第一个线程获取锁,调试结果如下,可以看到Thread-A即持有锁的线程在sync的属性里,而链表的头节点不记录线程信息但是状态为SIGNAL,而Thread-B的状态也为SINGAL,其后继节点Thread-C的状态就是默认状态0
AQS解锁逻辑
ReentrantLock.unlock
public void unlock() {sync.release(1);}
AbstractQueuedSynchronizer#release
public final boolean release(int arg) {if (tryRelease(arg)) {Node h = head;if (h != null && h.waitStatus != 0)unparkSuccessor(h);return true;}return false;}
- 获取头节点,并调用头节点的unparkSuccessor,唤醒链表中的第二个节点
AbstractQueuedSynchronizer#unparkSuccessor
private void unparkSuccessor(Node node) {int ws = node.waitStatus;if (ws < 0)node.compareAndSetWaitStatus(ws, 0);Node s = node.next;if (s == null || s.waitStatus > 0) {s = null;for (Node p = tail; p != node && p != null; p = p.prev)if (p.waitStatus <= 0)s = p;}if (s != null)LockSupport.unpark(s.thread);}
- 先cas置换status
- 获取后继节点,如果判断这个后继节点是null或者是cancel状态,说明该节点已经失效,那么就会从尾部开始向前找最接近头部的SINGAL节点或者状态是0的节点(那就是在队尾),这个很好理解,我们可以想象链表头节点往后的一段全是cancel,那么就是找到这一段cancel后的第一个singal节点并唤醒它,至于为什么从尾部开始向前找,是因为AQS指针连接的问题,这里就没有再深挖了
总结
我们再举一个例子,现在有四个线程,依次是1,2,3,4,其中只有2是cancel状态,1占有锁,当1释放锁后会是什么流程
- 根据解锁逻辑,会先找到3这个节点
- 此时unpark这个3节点,3节点回到acquireQueued这个方法里,进入第一个if:
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) { setHead(node); p.next = null; // help GCreturn interrupted;
}
可以看到获取前驱节点,3的前驱是2,而2不是头节点,不会进入这个if,自然也不会尝试获取锁,所以会再次进入shouldParkAfterFailedAcquire
- 当进入shouldParkAfterFailedAcquire,我们前面分析了它会从删除已经cancel的所有前驱节点,也就是说2节点会在这里面被移除了
- 当2节点被移除后,此时再循环一次acquireQueued,这个时候3的前驱就是1节点也就是头节点了, 就可以正常获取锁了
相关文章:

AQS公平锁与非公平锁之源码解析
AQS加锁逻辑 ReentrantLock.lock public void lock() {sync.acquire(1);}AbstractQueuedSynchronizer#acquire public final void acquire(int arg) {if (!tryAcquire(arg) &&acquireQueued(addWaiter(Node.EXCLUSIVE), arg))selfInterrupt();}addWaiter就是将节点加入…...
若依框架在企业中的应用调研
若依框架作为一款基于 Spring Boot 的轻量级 Java 快速开发框架,在企业级应用开发中发挥着重要作用。以下是对其在企业中应用的调研情况: 应用现状 广泛应用于多种管理系统:在众多企业中,若依框架常被用于构建各类后台管理系统&a…...
【Day23 LeetCode】贪心算法题
一、贪心算法 贪心没有套路,只有碰运气(bushi),举反例看看是否可行,(运气好)刚好贪心策略的局部最优就是全局最优。 1、分发饼干 455 思路:按照孩子的胃口从小到大的顺序依次满足…...

2025年PHP面试宝典,技术总结。
面试是进入职场的第一道坎,因为我本身学校太一般的问题在面试中遇到了各种不爽,和那些高学历的相比自己真是信心大跌。我面试的方向是php开发工程师,主要做网站后台、APP接口等。下面是我这段时间总结的面试方面的常考常问的知识点࿰…...

Qt中的按钮组:QPushButton、QToolButton、QRadioButton和QCheckBox使用方法(详细图文教程)
💪 图像算法工程师,专业从事且热爱图像处理,图像处理专栏更新如下👇: 📝《图像去噪》 📝《超分辨率重建》 📝《语义分割》 📝《风格迁移》 📝《目标检测》 &a…...

influxdb+grafana+jmeter
influxdb influxd先启动 启动完成后执行 influxdb的端口号 grafana的启动 通过grafana-server.exe启动grafana 启动后打开 http://localhost:8087/...

Net Core微服务入门全纪录(三)——Consul-服务注册与发现(下)
系列文章目录 1、.Net Core微服务入门系列(一)——项目搭建 2、.Net Core微服务入门全纪录(二)——Consul-服务注册与发现(上) 3、.Net Core微服务入门全纪录(三)——Consul-服务注…...

leetcode 479. 最大回文数乘积
题目如下 看完题目后没有想到取巧的办法所以尝试使用枚举法。 使用枚举法之前先回答两个问题: 1. 如何构造回文串? 2. 如何判断是否存在两个n位整数相乘可以得到这个回文串? 显然n位数与n位数相乘必然是2n位数也就是说最大回文整数长度必然…...

独立搭建UI自动化测试框架
🍅 点击文末小卡片,免费获取软件测试全套资料,资料在手,涨薪更快 今天给大家分享一个seleniumtestngmavenant的UI自动化,可以用于功能测试,也可按复杂的业务流程编写测试用例,今天此篇文章不过多…...

62,【2】 BUUCTF WEB [强网杯 2019]Upload1
进入靶场 此处考点不是SQL,就正常注册并登录进去 先随便传一个 进行目录扫描,我先用爆破代替 先随便后面写个文件名 为了提供payload位置 www.tar.gz真的存在 返回浏览器修改url就自动下载了 看到tp5,应该是ThinkPHP5框架 参考此博客的思路方法c[强网杯…...

Spring Boot 整合 ShedLock 处理定时任务重复执行的问题
🌷 古之立大事者,不惟有超世之才,亦必有坚忍不拔之志 🎐 个人CSND主页——Micro麦可乐的博客 🐥《Docker实操教程》专栏以最新的Centos版本为基础进行Docker实操教程,入门到实战 🌺《RabbitMQ》…...

常见Arthas命令与实践
Arthas 官网:https://arthas.aliyun.com/doc/,官方文档对 Arthas 的每个命令都做出了介绍和解释,并且还有在线教程,方便学习和熟悉命令。 Arthas Idea 的 IDEA 插件。 这是一款能快速生成 Arthas命令的插件,可快速生成…...
Glide加载gif遇到的几个坑
Glide本身支持gif格式的动画加载,但是大多数情况下我们用Glide都是去加载一些静态图片,加载gif动态图的需求不是很多,因此这次使用Glide加载gif就遇到了一些令人匪夷所思的问题 问题一:加载gif图片会有明显的卡顿 通常情况下我们…...

STM32学习之通用定时器
1.1通用定时器介绍 通用定时器具有基本定时器的所有特征,基本定时器只能递增计数,而通用定时器可以递减计数,可以中心对齐计数;也可以触发ADC和DAC,同时在更新事件,触发事件,输入捕获ÿ…...

MiniMax-Text-01——模型详细解读与使用
MiniMax发布了最新的旗舰款模型,MiniMax-Text-01。这是一个456B参数的MOE模型,支持最大4M上下文。今天我们来解读一下这个模型,最后会讲一下模型的使用方式和价格。 先来看整体指标,以下图表分为三块指标,分别是文本能…...

Redis的Windows版本安装以及可视化工具
文章目录 redis安装redis安装包下载解压文件夹启动redis服务Redis路径配置环境变量打开redis客户端进行连接基础操作测试 redis可视化工具下载Redis Desktop Manager redis安装 redis安装包下载 windows版本readis下载:Releases tporadowski/redis 解压文件夹 我…...

tensorflow源码编译在C++环境使用
https://tensorflow.google.cn/install/source?hlzh-cn查看tensorflow和其他需要下载软件对应的版本,最好一模一样 1、下载TensorFlow源码 https://github.com/tensorflow/tensorflow 2、安装编译protobuf(3.9.2) protobuf版本要和TensorFlo…...
第四届机器学习、云计算与智能挖掘国际会议
一、会议信息 会议名称:第四届机器学习、云计算与智能挖掘国际会议(MLCCIM 2025) 会议地点:中国漠河 会议时间:2025年7月21-25日 支持单位:佛山市人工智能学会、佛山大学 二、大会主席 …...

#漏洞挖掘# 一文了解什么是Jenkins未授权访问!!!
免责声明 本教程仅为合法的教学目的而准备,严禁用于任何形式的违法犯罪活动及其他商业行为,在使用本教程前,您应确保该行为符合当地的法律法规,继续阅读即表示您需自行承担所有操作的后果,如有异议,请立即停…...

QT QListWidget控件 全面详解
本系列文章全面的介绍了QT中的57种控件的使用方法以及示例,包括 Button(PushButton、toolButton、radioButton、checkBox、commandLinkButton、buttonBox)、Layouts(verticalLayout、horizontalLayout、gridLayout、formLayout)、Spacers(verticalSpacer、horizontalSpacer)、…...

【Vim Masterclass 笔记25】S10L45:Vim 多窗口的常用操作方法及相关注意事项
文章目录 S10L45 Working with Multiple Windows1 水平分割窗口2 在水平分割的新窗口中显示其它文件内容3 垂直分割窗口4 窗口的关闭5 在同一窗口水平拆分出多个窗口6 关闭其余窗口7 让四个文件呈田字形排列8 光标在多窗口中的定位9 调节子窗口的尺寸大小10 变换子窗口的位置11…...

包文件分析器 Webpack Bundle Analyzer
webpack-bundle-analyzer 是一个非常有用的工具,用于可视化和分析 Webpack 打包生成的文件。这使得开发者能够更好地理解应用的依赖关系、包的大小,以及优化打包的机会。以下是关于 webpack-bundle-analyzer 的详细介绍,包括它的安装、使用以…...
代码随想录day14
二叉树的反转,采用迭代,只能用前序和后序遍历 /*** Definition for a binary tree node.* struct TreeNode {* int val;* TreeNode *left;* TreeNode *right;* TreeNode() : val(0), left(nullptr), right(nullptr) {}* TreeNode(i…...
react19新API之use()用法总结
React use() Hook 使用指南 概述 use() 是 React 19 引入的新 Hook,它允许你在组件内部直接使用 Promise、Context 和其他可订阅的值。它是一个更通用的数据获取和订阅机制。 基本语法 const value use(resource);主要用途 1. Promise 处理 function UserDet…...

67,【7】buuctf web [HarekazeCTF2019]Avatar Uploader 2(未完成版)
进入靶场 和上一题一母同胞,先把上一题的答案拖进去看看 区别在于上一题这块直接显示了flag,这里并没有 看看源码 加载不出来,ctrlu <!-- 上传头像的提示信息,说明上传要求 --><p>Please upload a PNG image less th…...

ANSYS HFSS 中的相控天线阵列仿真方法
概述 相控天线阵列系统广泛使用,从国防雷达应用到商业 5G 应用。设计这些天线阵列涉及复杂的数学运算,需要全波仿真。Ansys HFSS 全场 3D 电磁仿真软件可以在合理的时间内以较低的计算成本仿真复杂的相控阵天线系统,同时考虑复杂激励、环境&…...

stm32 L051 adc配置及代码实例解析
一 cude的设置: 1. 接口的基本设置: 2. 参数的设置: 二 代码的逻辑: 1. 上面的直接生成代码,然后使用下面源码即可读到adc的数据: void adc_battery_start(void) {uint32_t ADC_value 0;HAL_ADC_Start(&…...

KUKA示教器仿真软件OfficeLite8.6.2,EthernetKRL3.1.3通信
一、准备软件。 1、vmware17.6.1 2、OfficeLite8.6.2 3、EthernetKRL3.1.3 4、KUKA Router 5、EthernetKRL_Server 通过网盘分享的文件:库卡相关软件 链接: https://pan.baidu.com/s/1NwvR3RVP0edLBeZnnnCYvw 提取码: smys 二、安装vmware17.6.1 1、找到下载…...
Erlang语言的并发编程
Erlang语言的并发编程 引言 并发编程是现代软件开发中的一个重要领域,尤其是在面对需要高效处理大量任务的应用时。Erlang是一种专门设计用于并发编程的编程语言,由于其在电信和即时通信系统中的广泛应用,逐渐引起了开发者的关注。Erlang的…...

【数据挖掘实战】 房价预测
本次对kaggle中的入门级数据集,房价回归数据集进行数据挖掘,预测房屋价格。 本人主页:机器学习司猫白 机器学习专栏:机器学习实战 PyTorch入门专栏:PyTorch入门 深度学习实战:深度学习 ok,话不多…...