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节点也就是头节点了, 就可以正常获取锁了

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

数字电子技术基础(十五)——MOS管的简单介绍

目录 1 MOS的简单介绍 1.1 MOS简介 1.2 MOS管的基本结构 1.3 MOS管工作时的三个区域 1.4 MOSEF的结构的工作原理 1 MOS的简单介绍 1.1 MOS简介 绝缘栅型场效应管&#xff0c;简称MOS管&#xff0c;全称为金属-氧化物-半导体场效应晶体管&#xff08;Metal-Oxide-Semic…

基础入门-传输加密数据格式编码算法密文存储代码混淆逆向保护安全影响

知识点&#xff1a; 1、传输格式&传输数据-类型&编码&算法 2、密码存储&代码混淆-不可逆&非对称性 一、演示案例-传输格式&传输数据-类型&编码&算法 传输格式 JSON XML WebSockets HTML 二进制 自定义 WebSockets&#xff1a;聊天交互较常…

Spark/Kafka

文章目录 项目地址一、Spark1. RDD1.1 五大核心属性1.2 执行原理1.3 四种创建方式二、Kafka2.1 生产者(1)分区器(2)生产者提高吞吐量(3) 生产者数据可靠性数据传递语义幂等性和事务数据有序2.2 Broker(1)Broker工作流程(2)节点服役和退役2.3 副本(1)Follower故障细…

10倍数据交付提升 | 通过逻辑数据仓库和数据编织高效管理和利用大数据

数据已经成为企业核心竞争力的关键要素。随着大数据技术的发展&#xff0c;如何高效管理和利用海量的数据&#xff0c;已成为企业在数字化转型过程中面临的重要课题。传统的数据仓库已经不能满足当今企业对数据处理的高效性、灵活性和实时性的需求。在这种背景下&#xff0c;逻…

《keras 3 内卷神经网络》

keras 3 内卷神经网络 作者&#xff1a;Aritra Roy Gosthipaty 创建日期&#xff1a;2021/07/25 最后修改时间&#xff1a;2021/07/25 描述&#xff1a;深入研究特定于位置和通道无关的“内卷”内核。 &#xff08;i&#xff09; 此示例使用 Keras 3 在 Colab 中查看 GitHub …

Unreal Engine 5 C++ Advanced Action RPG 十章笔记

第十章 Survival Game Mode 2-Game Mode Test Map 设置游戏规则进行游戏玩法 生成敌人玩家是否死亡敌人死亡是否需要刷出更多 肯定:难度增加否定:玩家胜利 流程 新的游戏模式类游戏状态新的数据表来指定总共有多少波敌人生成逻辑UI告诉当前玩家的敌人波数 3-Survival Game M…

嵌入式产品级-超小尺寸热成像相机(从0到1 硬件-软件-外壳)

Thermal_Imaging_Camera This is a small thermal imaging camera that includes everything from hardware and software. 小尺寸热成像相机-Pico-LVGL-RTOS 基于RP2040 Pico主控与RTOS&#xff0c;榨干双核性能实现LVGL和成图任务并行。ST7789驱动240280屏&#xff0c;CST8…

AI守护煤矿安全生产:基于视频智能的煤矿管理系统架构解析

前言 本文我将介绍我和我的团队自主研发设计的一款AI产品的成果展示——“基于视频AI识别技术的煤矿安全生产管理系统”。 这款产品是目前我在创业阶段和几位矿业大学的博士共同从架构设计、开发到交付的全过程中首次在博客频道发布, 我之前一直想写但没有机会来整理这套系统的…

OCCT 之 TDF_Attribute 以及子类

一.概述 TDF_Label是OCAF中核心数据结构&#xff0c;与TDF_Attribute结合使用&#xff0c;实现对模型的各种操作。 以下摘自OCCT7.7.0官方文档 A class each application has to implement. It is used to contain the application data. This abstract class, alongwith La…

数字化时代,传统代理模式的变革之路

在数字化飞速发展的今天&#xff0c;线上线下融合&#xff08;O2O&#xff09;成了商业领域的大趋势。这股潮流&#xff0c;正猛烈冲击着传统代理模式&#xff0c;给它带来了新的改变。 咱们先看看线上线下融合现在啥情况。线上渠道那是越来越多&#xff0c;企业纷纷在电商平台…

Vue2+OpenLayers添加缩放、滑块缩放、拾取坐标、鹰眼、全屏控件(提供Gitee源码)

目录 一、案例截图 二、安装OpenLayers库 三、代码实现 四、Gitee源码 一、案例截图 二、安装OpenLayers库 npm install ol 三、代码实现 废话不多说&#xff0c;直接给完整代码&#xff0c;替换成自己的KEY即可运行&#xff1a; <template><div><div i…

Vulnhub-Tr0ll靶机笔记

Tr0ll靶机笔记 概述 靶机地址&#xff1a;https://www.vulnhub.com/entry/tr0ll-1,100/ 这台靶机比较简单&#xff0c;包含ftp的渗透&#xff0c;pcap流量包的分析&#xff0c;常规的web渗透和系统内核提权。让我们开始吧 Hack it&#xff01; 一、nmap扫描 1、端口扫描 …

高效建站指南:通过Portainer快速搭建自己的在线网站

文章目录 前言1. 安装Portainer1.1 访问Portainer Web界面 2. 使用Portainer创建Nginx容器3. 将Web静态站点实现公网访问4. 配置Web站点公网访问地址4.1公网访问Web站点 5. 固定Web静态站点公网地址6. 固定公网地址访问Web静态站点 前言 Portainer是一个开源的Docker轻量级可视…

Docker Compose的使用

文章首发于我的博客&#xff1a;https://blog.liuzijian.com/post/docker-compose.html 目录 Docker Compose是什么Docker Compose安装Docker Compose文件Docker Compose常用命令案例&#xff1a;部署WordPress博客系统 Docker Compose是什么 Docker Compose是Docker官方的开源…

JDK长期支持版本(LTS)

https://blogs.oracle.com/java/post/the-arrival-of-java-23 jdk长期支持版本&#xff08;LTS&#xff09;&#xff1a;JDK 8、11、17、21&#xff1a;

python(25) : 含有大模型生成的公式的文本渲染成图片并生成word文档(支持flask接口调用)

公式样例 渲染前 \[\sqrt{1904.615384} \approx 43.64\] 渲染后 安装依赖 pip install matplotlib -i https://mirrors.aliyun.com/pypi/simple/ requestspip install sympy -i https://mirrors.aliyun.com/pypi/simple/ requestspip install python-docx -i https://mirro…

SSM宠物医院信息管理系统

&#x1f345;点赞收藏关注 → 添加文档最下方联系方式咨询本源代码、数据库&#x1f345; 本人在Java毕业设计领域有多年的经验&#xff0c;陆续会更新更多优质的Java实战项目希望你能有所收获&#xff0c;少走一些弯路。&#x1f345;关注我不迷路&#x1f345; 项目视频 宠…

mysql查看binlog日志

mysql 配置、查看binlog日志&#xff1a; 示例为MySQL8.0 1、 检查binlog开启状态 SHOW VARIABLES LIKE ‘log_bin’; 如果未开启&#xff0c;修改配置my.ini 开启日志 安装目录配置my.ini(mysql8在data目录) log-binmysql-bin&#xff08;开启日志并指定日志前缀&#xff…

某国际大型超市电商销售数据分析和可视化

完整源码项目包获取→点击文章末尾名片&#xff01; 本作品将从人、货、场三个维度&#xff0c;即客户维度、产品维度、区域维度&#xff08;补充时间维度与其他维度&#xff09;对某国际大型超市的销售情况进行数据分析和可视化报告展示&#xff0c;从而为该超市在弄清用户消费…

PostgreSQL-01-入门篇-简介

文章目录 1. PostgreSQL是什么?2. PostgreSQL 历史 2.1. 伯克利 POSTGRES 项目2.2. Postgres952.3. PostgreSQL来了 3. PostgreSQL vs MySQL4. 安装 4.1 Windows 安装4.2 linux 安装4.3 docker安装 1. PostgreSQL是什么 PostgreSQL 是一个基于加州大学伯克利分校计算机系开…