当前位置: 首页 > news >正文

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就是将节点加入队列的尾部,我们先看看非公平锁NonfairSynctryAcquire

	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

公平锁fairSynctryAcquire

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

![[Pasted image 20250121170956.png]]

  1. 再一次调用tryAcquire这个方法,尝试获取锁,获取成功后将该节点设置为头节点,两个结论:1. cas失败两次会进行阻塞,2. 链表中持有锁的节点就是头节点
  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;}

![[Pasted image 20250121171505.png]]

  • 五个状态:
状态作用
CANCELLED1表示该节点已经取消等待,不再参与锁竞争
SIGNAL-1表示后续节点需要被唤醒
CONDITION-2该节点在等待条件队列中
PROPAGATE-3共享模式下传播信号,让后续线程继续执行
默认值0节点刚入队列时的状态,当节点是队尾,状态就是0
  • 回到shouldParkAfterFailedAcquire方法,当前驱节点状态时SINGAL时,说明前驱节点将锁释放了,将唤醒当前节点(唤醒意味着该节点重新参与竞争锁,因为如果是非公平锁仍需要竞争),当前的线程不应该park,应该回到前面的for循环继续tryacquire
  • 如果状态大于0,说明前驱节点已经cancel,这个节点应该移除链表,可以看到这里的while会将其移除链表
  • 如果状态此时小于0等于了,说明这个前驱节点是正常的,将其设置为SINGAL状态,意为下次会唤醒当前节点
  1. 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
    ![[Pasted image 20250121175854.png]]

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释放锁后会是什么流程

  1. 根据解锁逻辑,会先找到3这个节点
  2. 此时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 快速开发框架&#xff0c;在企业级应用开发中发挥着重要作用。以下是对其在企业中应用的调研情况&#xff1a; 应用现状 广泛应用于多种管理系统&#xff1a;在众多企业中&#xff0c;若依框架常被用于构建各类后台管理系统&a…...

【Day23 LeetCode】贪心算法题

一、贪心算法 贪心没有套路&#xff0c;只有碰运气&#xff08;bushi&#xff09;&#xff0c;举反例看看是否可行&#xff0c;&#xff08;运气好&#xff09;刚好贪心策略的局部最优就是全局最优。 1、分发饼干 455 思路&#xff1a;按照孩子的胃口从小到大的顺序依次满足…...

2025年PHP面试宝典,技术总结。

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

Qt中的按钮组:QPushButton、QToolButton、QRadioButton和QCheckBox使用方法(详细图文教程)

&#x1f4aa; 图像算法工程师&#xff0c;专业从事且热爱图像处理&#xff0c;图像处理专栏更新如下&#x1f447;&#xff1a; &#x1f4dd;《图像去噪》 &#x1f4dd;《超分辨率重建》 &#x1f4dd;《语义分割》 &#x1f4dd;《风格迁移》 &#x1f4dd;《目标检测》 &a…...

influxdb+grafana+jmeter

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

Net Core微服务入门全纪录(三)——Consul-服务注册与发现(下)

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

leetcode 479. 最大回文数乘积

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

独立搭建UI自动化测试框架

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

62,【2】 BUUCTF WEB [强网杯 2019]Upload1

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

Spring Boot 整合 ShedLock 处理定时任务重复执行的问题

&#x1f337; 古之立大事者&#xff0c;不惟有超世之才&#xff0c;亦必有坚忍不拔之志 &#x1f390; 个人CSND主页——Micro麦可乐的博客 &#x1f425;《Docker实操教程》专栏以最新的Centos版本为基础进行Docker实操教程&#xff0c;入门到实战 &#x1f33a;《RabbitMQ》…...

常见Arthas命令与实践

Arthas 官网&#xff1a;https://arthas.aliyun.com/doc/&#xff0c;官方文档对 Arthas 的每个命令都做出了介绍和解释&#xff0c;并且还有在线教程&#xff0c;方便学习和熟悉命令。 Arthas Idea 的 IDEA 插件。 这是一款能快速生成 Arthas命令的插件&#xff0c;可快速生成…...

Glide加载gif遇到的几个坑

Glide本身支持gif格式的动画加载&#xff0c;但是大多数情况下我们用Glide都是去加载一些静态图片&#xff0c;加载gif动态图的需求不是很多&#xff0c;因此这次使用Glide加载gif就遇到了一些令人匪夷所思的问题 问题一&#xff1a;加载gif图片会有明显的卡顿 通常情况下我们…...

STM32学习之通用定时器

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

MiniMax-Text-01——模型详细解读与使用

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

Redis的Windows版本安装以及可视化工具

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

tensorflow源码编译在C++环境使用

https://tensorflow.google.cn/install/source?hlzh-cn查看tensorflow和其他需要下载软件对应的版本&#xff0c;最好一模一样 1、下载TensorFlow源码 https://github.com/tensorflow/tensorflow 2、安装编译protobuf&#xff08;3.9.2&#xff09; protobuf版本要和TensorFlo…...

第四届机器学习、云计算与智能挖掘国际会议

一、会议信息 会议名称&#xff1a;第四届机器学习、云计算与智能挖掘国际会议&#xff08;MLCCIM 2025&#xff09;​​​​​​​ 会议地点&#xff1a;中国漠河 会议时间&#xff1a;2025年7月21-25日 支持单位&#xff1a;佛山市人工智能学会、佛山大学 二、大会主席 …...

#漏洞挖掘# 一文了解什么是Jenkins未授权访问!!!

免责声明 本教程仅为合法的教学目的而准备&#xff0c;严禁用于任何形式的违法犯罪活动及其他商业行为&#xff0c;在使用本教程前&#xff0c;您应确保该行为符合当地的法律法规&#xff0c;继续阅读即表示您需自行承担所有操作的后果&#xff0c;如有异议&#xff0c;请立即停…...

QT QListWidget控件 全面详解

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

Python实现prophet 理论及参数优化

文章目录 Prophet理论及模型参数介绍Python代码完整实现prophet 添加外部数据进行模型优化 之前初步学习prophet的时候&#xff0c;写过一篇简单实现&#xff0c;后期随着对该模型的深入研究&#xff0c;本次记录涉及到prophet 的公式以及参数调优&#xff0c;从公式可以更直观…...

江苏艾立泰跨国资源接力:废料变黄金的绿色供应链革命

在华东塑料包装行业面临限塑令深度调整的背景下&#xff0c;江苏艾立泰以一场跨国资源接力的创新实践&#xff0c;重新定义了绿色供应链的边界。 跨国回收网络&#xff1a;废料变黄金的全球棋局 艾立泰在欧洲、东南亚建立再生塑料回收点&#xff0c;将海外废弃包装箱通过标准…...

第25节 Node.js 断言测试

Node.js的assert模块主要用于编写程序的单元测试时使用&#xff0c;通过断言可以提早发现和排查出错误。 稳定性: 5 - 锁定 这个模块可用于应用的单元测试&#xff0c;通过 require(assert) 可以使用这个模块。 assert.fail(actual, expected, message, operator) 使用参数…...

动态 Web 开发技术入门篇

一、HTTP 协议核心 1.1 HTTP 基础 协议全称 &#xff1a;HyperText Transfer Protocol&#xff08;超文本传输协议&#xff09; 默认端口 &#xff1a;HTTP 使用 80 端口&#xff0c;HTTPS 使用 443 端口。 请求方法 &#xff1a; GET &#xff1a;用于获取资源&#xff0c;…...

【从零学习JVM|第三篇】类的生命周期(高频面试题)

前言&#xff1a; 在Java编程中&#xff0c;类的生命周期是指类从被加载到内存中开始&#xff0c;到被卸载出内存为止的整个过程。了解类的生命周期对于理解Java程序的运行机制以及性能优化非常重要。本文会深入探寻类的生命周期&#xff0c;让读者对此有深刻印象。 目录 ​…...

算法刷题-回溯

今天给大家分享的还是一道关于dfs回溯的问题&#xff0c;对于这类问题大家还是要多刷和总结&#xff0c;总体难度还是偏大。 对于回溯问题有几个关键点&#xff1a; 1.首先对于这类回溯可以节点可以随机选择的问题&#xff0c;要做mian函数中循环调用dfs&#xff08;i&#x…...

用鸿蒙HarmonyOS5实现国际象棋小游戏的过程

下面是一个基于鸿蒙OS (HarmonyOS) 的国际象棋小游戏的完整实现代码&#xff0c;使用Java语言和鸿蒙的Ability框架。 1. 项目结构 /src/main/java/com/example/chess/├── MainAbilitySlice.java // 主界面逻辑├── ChessView.java // 游戏视图和逻辑├── …...

自定义线程池1.2

自定义线程池 1.2 1. 简介 上次我们实现了 1.1 版本&#xff0c;将线程池中的线程数量交给使用者决定&#xff0c;并且将线程的创建延迟到任务提交的时候&#xff0c;在本文中我们将对这个版本进行如下的优化&#xff1a; 在新建线程时交给线程一个任务。让线程在某种情况下…...

若依项目部署--传统架构--未完待续

若依项目介绍 项目源码获取 #Git工具下载 dnf -y install git #若依项目获取 git clone https://gitee.com/y_project/RuoYi-Vue.git项目背景 随着企业信息化需求的增加&#xff0c;传统开发模式存在效率低&#xff0c;重复劳动多等问题。若依项目通过整合主流技术框架&…...

统计按位或能得到最大值的子集数目

我们先来看题目描述&#xff1a; 给你一个整数数组 nums &#xff0c;请你找出 nums 子集 按位或 可能得到的 最大值 &#xff0c;并返回按位或能得到最大值的 不同非空子集的数目 。 如果数组 a 可以由数组 b 删除一些元素&#xff08;或不删除&#xff09;得到&#xff0c;…...