释放锁流程源码剖析
1 释放锁流程概述
ReentrantLock的unlock()方法不区分公平锁还是非公平锁。
-
首先调用unlock()方法。
-
unlock()底层使用的是Sync.release(1)方法
-
public void unlock() {<!-- -->
sync.release(1);
}
release(1)方法会调用tryRelease(1)去尝试解锁。
public final boolean release(int arg) {<!-- -->//尝试释放锁if (tryRelease(arg)) {<!-- -->Node h = head;if (h != null && h.waitStatus != 0)//如果释放锁成功,而且等待队列不为空,且有一个以上的等待线程//因为只有下一个线程才能将前一个线程的waitStatus的状态改为-1,head表示当前执行的线程//当head不为空,且waitStatus !=0说明有等待线程初始化了等待队列,且将持有锁线程的//等待状态改为了-1,必然存在等待线程,将队头的第一个唤醒unparkSuccessor(h);return true;}return false;}
tryRelease(arg)尝试释放锁
@ReservedStackAccessprotected final boolean tryRelease(int releases) {<!-- -->//释放一次锁,就将重入的次数减掉1int c = getState() - releases;if (Thread.currentThread() != getExclusiveOwnerThread())throw new IllegalMonitorStateException();boolean free = false;//如果锁得状态为1,则表示锁真正被释放了,将持有锁的线程置为nullif (c == 0) {<!-- -->free = true;setExclusiveOwnerThread(null);}//否则,锁依然被持有,因为该锁被持锁线程重入了多次setState(c);return free;}
如果tryRelease()释放锁成功,且判断等待队列确实有阻塞线程,则尝试唤醒
private void unparkSuccessor(Node node) {<!-- -->//如果等待的线程状态<0,SIGNAL,将其设为0int 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);}
回答自己的疑问,为啥没有操作头节点呢?这是因为唤醒阻塞的第一个线程后,它会重新去获取锁,而不是直接将锁分配给它。
final boolean acquireQueued(final Node node, int arg) {<!-- -->boolean interrupted = false;try {<!-- -->for (;;) {<!-- -->final Node p = node.predecessor();if (p == head && tryAcquire(arg)) {<!-- -->setHead(node);p.next = null; // help GCreturn interrupted;}if (shouldParkAfterFailedAcquire(p, node))//从此处被唤醒后,重新进行循环,尝试去争抢锁,如果没抢到,则继续阻塞(非公平的时候)//当刚被唤醒,循环一次,此时p==head,同时如果tryAcquire(1)去获得锁,//如果获得成功将自己设置为head,//如果获得锁失败,则自己再自旋一次(因为在释放锁的时候,head的ws又重置为0了).//如果还是失败,则自己再次park()睡眠interrupted |= parkAndCheckInterrupt();}} catch (Throwable t) {<!-- -->cancelAcquire(node);if (interrupted)selfInterrupt();throw t;}}
2 释放锁源码分析
public void unlock() {
// 释放锁资源不分为公平锁和非公平锁,都是一个sync对象
sync.release(1);
}
// 释放锁的核心流程
public final boolean release(int arg) {
// 核心释放锁资源的操作之一
if (tryRelease(arg)) {
// 如果锁已经释放掉了,走这个逻辑
Node h = head;
// h不为null,说明有排队的(录课时估计脑袋蒙圈圈。)
// 如果h的状态不为0(为-1),说明后面有排队的Node,并且线程已经挂起了。
if (h != null && h.waitStatus != 0)// 唤醒排队的线程
unparkSuccessor(h);
return true;
}
return false;
}
// ReentrantLock释放锁资源操作
protected final boolean tryRelease(int releases) {
// 拿到state - 1(并没有赋值给state)
int c = getState() - releases;
// 判断当前持有锁的线程是否是当前线程,如果不是,直接抛出异常
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
// free,代表当前锁资源是否释放干净了。
boolean free = false;
if (c == 0) {
// 如果state - 1后的值为0,代表释放干净了。
free = true;
// 将持有锁的线程置位null
setExclusiveOwnerThread(null);
}
// 将c设置给state
setState(c);
// 锁资源释放干净返回true,否则返回false
return free;
}
// 唤醒后面排队的Node
private void unparkSuccessor(Node node) {
// 拿到头节点状态
int ws = node.waitStatus;
if (ws < 0)
// 先基于CAS,将节点状态从-1,改为0
compareAndSetWaitStatus(node, ws, 0);
// 拿到头节点的后续节点。
Node s = node.next;
// 如果后续节点为null或者,后续节点的状态为1,代表节点取消了。
if (s == null || s.waitStatus > 0) {
s = null;
// 如果后续节点为null,或者后续节点状态为取消状态,从后往前找到一个有效节点环境
for (Node t = tail; t != null && t != node; t = t.prev)
// 从后往前找到状态小于等于0的节点
// 找到离head最新的有效节点,并赋值给s
if (t.waitStatus <= 0)
s = t;
}
// 只要找到了这个需要被唤醒的节点,执行unpark唤醒
if (s != null)
LockSupport.unpark(s.thread);
}
3 AQS常见的问题
3.1 AQS中为什么要有一个虚拟的head节点
因为AQS提供了ReentrantLock的基本实现,而在ReentrantLock释放锁资源时,需要去考虑是否需要执行unparkSuccessor方法,去唤醒后继节点。
因为Node中存在waitStatus的状态,默认情况下状态为0,如果当前节点的后继节点线程挂起了,那么就将当前节点的状态设置为-1。这个-1状态的出现是为了避免重复唤醒或者释放资源的问题。
因为AQS中排队的Node中的线程如果挂起了,是无法自动唤醒的。需要释放锁或者释放资源后,再被释放的线程去唤醒挂起的线程。 因为唤醒节点需要从整个AQS双向链表中找到离head最近的有效节点去唤醒。而这个找离head最近的Node可能需要遍历整个双向链表。如果AQS中,没有挂起的线程,代表不需要去遍历AQS双向链表去找离head最近的有效节点。为了避免出现不必要的循环链表操作,提供了一个-1的状态。如果只有一个Node进入到AQS中排队,所以发现如果是第一个Node进来,他必须先初始化一个虚拟的head节点作为头,来监控后继节点中是否有挂起的线程。
3. 2 AQS中为什么选择使用双向链表,而不是单向链表
首先AQS中一般是存放没有获取到资源的Node,而在竞争锁资源时,ReentrantLock提供了一个方法,lockInterruptibly方法,也就是线程在竞争锁资源的排队途中,允许中断。中断后会执行cancelAcquire方法,从而将当前节点状态置位1,并且从AQS队列中移除掉。如果采用单向链表,当前节点只能按到后继或者前继节点,这样是无法将前继节点指向后继节点的,需要遍历整个
AQS从头或者从尾去找。单向链表在移除AQS中排队的Node时,成本很高。
当前在唤醒后继节点时,如果是单向链表也会出问题,因为节点插入方式的问题,导致只能单向的去找有效节点去唤醒,从而造成很多次无效的遍历操作,如果是双向链表就可以解决这个问题。
相关文章:

释放锁流程源码剖析
1 释放锁流程概述 ReentrantLock的unlock()方法不区分公平锁还是非公平锁。 首先调用unlock()方法。 unlock()底层使用的是Sync.release(1)方法 public void unlock() {<!-- --> sync.release(1); } release(1)方法会调用tryRelease(1)去尝试解锁。 public fin…...

ComText让机器人有了情节记忆
为了让人类与机器人更好地交流,MIT 计算机科学与人工智能实验室的研究员开发了一个名为 ComText 的程序。这款程序给机器人增加了情节记忆,让它们能够接受更加复杂的命令。目前,他们已经在机器人 Baxter 上测试了程序。 机器人没有情景化的记…...

【Leetcode合集】13. 罗马数字转整数
13. 罗马数字转整数 13. 罗马数字转整数 代码仓库地址: https://github.com/slience-me/Leetcode 个人博客 :https://slienceme.xyz 罗马数字包含以下七种字符: I, V, X, L,C,D 和 M。 字符…...

centos oracle11g开启归档模式
要在 CentOS 上停止 Oracle 11g 数据库,你可以按照以下步骤操作: 1.登录到操作系统 首先,使用具有足够权限的用户登录到 CentOS 操作系统。通常情况下,你需要以具有 oracle 用户权限的用户登录。 使用 SYSDBA 权限连接到数据库…...

【数据结构初阶】双链表
双链表 1.双链表的实现1.1结口实现1.2申请结点1.3初始化双链表1.4打印双链表1.5尾插1.6尾删1.7头插1.8头删1.9计算大小1.10查找1.11pos位置插入1.12删除pos位置1.12删除双链表 全部码源 1.双链表的实现 1.1结口实现 #include<stdio.h> #include<stdlib.h> #inclu…...

Django实战:从零到一构建安全高效的Web应用
目录 一、概述 二、版本控制和部署 1、Git版本控制 2、Docker部署 三、数据库配置 1、配置数据库设置 2、创建数据库模型 四、URL路由和视图 1、定义URL路由 2、创建视图 五、模板渲染 1、创建模板 2、在视图中使用模板 总结 一、概述 Django是一个高级Python W…...

Docker build报错总结,版本过新大避雷!
1.速度太慢报错,需要换源; 在DOCKERFILE中添加镜像; RUN echo "deb http://mirror.sjtu.edu.cn/debian bookworm main non-free contrib" > /etc/apt/sources.list, 2.即使在Dockerfile中换源,但在bul…...

spider 网页爬虫中的 AWS 实例数据获取问题及解决方案
前言 AAWS实例数据对于自动化任务、监控、日志记录和资源管理非常重要。开发人员和运维人员可以通过AWS提供的API和控制台访问和管理这些数据,以便更好地管理和维护他们在AWS云上运行的实例。然而,在使用 spider 框架进行网页爬取时,我们常常…...

flink的window和windowAll的区别
背景 在flink的窗口函数运用中,window和windowAll方法总是会引起混淆,特别是结合上GlobalWindow的组合时,更是如此,本文就来梳理下他们的区别和常见用法 window和windowAll的区别 window是KeyStream数据流的方法,其…...

【机器学习】特征工程:特征选择、数据降维、PCA
各位同学好,今天我和大家分享一下python机器学习中的特征选择和数据降维。内容有: (1)过滤选择;(2)数据降维PCA;(3)sklearn实现 那我们开始吧。 一个数据集中…...

短视频账号矩阵系统saas管理私信回复管理系统
一、短视频矩阵号系统源码开发层面如何来解决? 1.短视频矩阵号系统源码搭建中,首先开发者需要保证api接口的稳定性 ,保证权限应用场景满足官方平台的开发预期。api---待发布、用户管理与授权绑定、私信回复与评论管理等是非常重要的权限接口。…...

利用ETLCloud自动化流程实现业务系统数据快速同步至数仓
现代企业有不少都完成了数字化的转型,而还未转型的企业或商铺也有进行数字化转型的趋势,由此可见,数据已经成为企业决策的重要依据。企业需要先获取数据,将业务系统数据同步至数仓进行整合,然后再进行数据分析。为了更…...

学习c#的第十六天
目录 C# 正则表达式 定义正则表达式 字符转义 字符类 定位点 分组构造 Lookaround 概览 数量词 反向引用构造 替换构造 替代 正则表达式选项 其他构造 Regex 类 代码示例 实例 1 实例 2 实例 3 C# 正则表达式 正则表达式 是一种匹配输入文本的模式。.Net 框…...

【论文阅读笔记】Deep learning for time series classification: a review
【论文阅读笔记】Deep learning for time series classification: a review 摘要 在这篇文章中,作者通过对TSC的最新DNN架构进行实证研究,探讨了深度学习算法在TSC中的当前最新性能。文章提供了对DNNs在TSC的统一分类体系下在各种时间序列领域中的最成功…...

如何将vscode和Linux远程链接:
如何将vscode和Linux远程链接: Remote - SSH - 远程登录Linux 安装Remote - SSH 我们下载完后,就会出现这些图标 这里点一下号 查看一下我们的主机名,并复制 输入ssh 用户名主机名 这里是要将ssh这个文件要放在主机下的哪个路径下ÿ…...

快速傅立叶卷积(FFC)
论文 LaMa: Resolution-robust Large Mask Inpainting with Fourier Convolutions https://github.com/advimman/lama 1.Introduce 解决图像绘制问题——缺失部分的真实填充——既需要“理解”自然图像的大尺度结构,又需要进行图像合成。 通常的做法是在一个大型自…...

藏头诗(C语言)
本题要求编写一个解密藏头诗的程序。 注:在 2022 年 7 月 14 日 16 点 50 分以后,该题数据修改为 UTF-8 编码。 输入格式: 输入为一首中文藏头诗,一共四句,每句一行。注意:一个汉字占三个字节。 输出格…...

适合您的智能手机的 7 款优秀手机数据恢复软件分享
如今,我们做什么都用手机;从拍照到录音,甚至作为 MP3 播放器,我们已经对手机变得非常依恋。这导致我们在手机上留下了很多珍贵的回忆。 不幸的是,我们有可能会丢失手机上的部分甚至全部数据。幸运的是,这不…...

uniapp APP下载流文件execl 并用WPS打开
使用plus.downloader.createDownload 方法将新建下载任务 HTML5 API Reference export default function plusDownload(config){if(!config){console.error("Argument should not be null");return;}const urlrequest.baseUrlconfig.url;let token uni.getStorage…...

【Python】 Python 操作PDF文档
Python 操作PDF文档 1、PDF (便携式文件格式,Portable Document Format)是由Adobe Systems在1993年用于文件交换所发展出的文件格式。 PDF主要由三项技术组成:衍生自PostScript;字型嵌入系统;资料压缩及传…...

vue3-响应式核心
🌈个人主页:前端青山 🔥系列专栏:Vue篇 🔖人终将被年少不可得之物困其一生 依旧青山,本期给大家带来vue篇专栏内容:vue3-响应式核心 响应式核心 目录 响应式核心 3.1ref() 3.2computed () 3.3 reactive() 3.4 …...

人工智能的广泛应用与影响
目录 前言1 智能手机与个人助手2 医疗保健3 自动驾驶技术4 金融领域5 教育与学习6 智能家居与物联网7 娱乐与媒体8 环境保护结语 前言 人工智能(Artificial Intelligence,AI)是当今科技领域的璀璨明星,它不仅在技术创新方面掀起了…...

SAP创建权限对象、角色、并分配角色
一、SU20:维护权限字段 二、SU21创建权限对象,分配权限字段: 三、SU24关联程序和自建权限对象(标准tcode会默认存在标准权限对象) 四、PFCG创建角色 五、SU01给用户分配角色 一、su20:维护权限字段 X点新建: 填入…...

[uni-app]记录APP端跳转页面自动滚动到底部的bug
文章目录 bug描述原因分析: 处理方案 bug描述 1.点击的A页面, 跳转到了B页面, 第一次页面正常显示 2.从B页面返回A页面 3.A页面不进行任何操作,再次点击A页面进入B页面 4.B页面自动滚动到底部. 原因 看一段A页面代码 let that thisthis.defaultScrollTop uni.getStorageSy…...

应用软件安全编程--23避免使用不安全的操作模式
块密码又称为分组加密, 一次加密明文中的一个块。将明文按一定的位长分组,明文组经过加密运 算得到密文组,密文组经过解密运算(加密运算的逆运算),还原成明文组。这种加密算法共有四种操作 模式用于描述如何重复地应用密码的单块操作来安全的…...

国产高云FPGA:纯verilog实现视频图像缩放,提供6套Gowin工程源码和技术支持
目录 1、前言免责声明 2、相关方案推荐国产高云FPGA相关方案推荐国产高云FPGA基础教程 3、设计思路框架视频源选择OV5640摄像头配置及采集动态彩条跨时钟FIFO图像缩放模块详解设计框图代码框图2种插值算法的整合与选择 Video Frame Buffer 图像缓存DDR3 Memory Interface 4、Go…...

python操作windows窗口,python库pygetwindow使用详解
文章目录 一、pygetwindow模块简介二、pygetwindow常用方法1、常用方法2、window常用方法 一、pygetwindow模块简介 pygetwindow是一个Python第三方库,用于获取、管理和操作窗口。它提供了一些方法和属性,使得在Python程序中可以轻松地执行各种窗口操作…...

手写消息队列(基于RabbitMQ)
一、什么是消息队列? 提到消息队列是否唤醒了你脑海深处的记忆?回看前面的这篇文章:《Java 多线程系列Ⅳ(单例模式阻塞式队列定时器线程池)》,其中我们在介绍阻塞队列时说过,阻塞队列最大的用途…...

kafka本地安装报错
Error: VM option ‘UseG1GC’ is experimental and must be enabled via -XX:UnlockExperimentalVMOptions. #打开 bin/kafka-run-class.sh KAFKA_JVM_PERFORMANCE_OPTS“-server -XX:UseG1GC -XX:MaxGCPauseMillis20 -XX:InitiatingHeapOccupancyPercent35 -XX:ExplicitGCInv…...

王者荣耀游戏
游戏运行如下: sxt Background package sxt;import java.awt.*; //背景类 public class Background extends GameObject{public Background(GameFrame gameFrame) {super(gameFrame);}Image bg Toolkit.getDefaultToolkit().getImage("C:\\Users\\24465\\D…...