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

释放锁流程源码剖析

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让机器人有了情节记忆

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

【Leetcode合集】13. 罗马数字转整数

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

centos oracle11g开启归档模式

要在 CentOS 上停止 Oracle 11g 数据库&#xff0c;你可以按照以下步骤操作&#xff1a; 1.登录到操作系统 首先&#xff0c;使用具有足够权限的用户登录到 CentOS 操作系统。通常情况下&#xff0c;你需要以具有 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.速度太慢报错&#xff0c;需要换源&#xff1b; 在DOCKERFILE中添加镜像&#xff1b; RUN echo "deb http://mirror.sjtu.edu.cn/debian bookworm main non-free contrib" > /etc/apt/sources.list&#xff0c; 2.即使在Dockerfile中换源&#xff0c;但在bul…...

spider 网页爬虫中的 AWS 实例数据获取问题及解决方案

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

flink的window和windowAll的区别

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

【机器学习】特征工程:特征选择、数据降维、PCA

各位同学好&#xff0c;今天我和大家分享一下python机器学习中的特征选择和数据降维。内容有&#xff1a; &#xff08;1&#xff09;过滤选择&#xff1b;&#xff08;2&#xff09;数据降维PCA&#xff1b;&#xff08;3&#xff09;sklearn实现 那我们开始吧。 一个数据集中…...

短视频账号矩阵系统saas管理私信回复管理系统

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

利用ETLCloud自动化流程实现业务系统数据快速同步至数仓

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

学习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 摘要 在这篇文章中&#xff0c;作者通过对TSC的最新DNN架构进行实证研究&#xff0c;探讨了深度学习算法在TSC中的当前最新性能。文章提供了对DNNs在TSC的统一分类体系下在各种时间序列领域中的最成功…...

如何将vscode和Linux远程链接:

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

快速傅立叶卷积(FFC)

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

藏头诗(C语言)

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

适合您的智能手机的 7 款优秀手机数据恢复软件分享

如今&#xff0c;我们做什么都用手机&#xff1b;从拍照到录音&#xff0c;甚至作为 MP3 播放器&#xff0c;我们已经对手机变得非常依恋。这导致我们在手机上留下了很多珍贵的回忆。 不幸的是&#xff0c;我们有可能会丢失手机上的部分甚至全部数据。幸运的是&#xff0c;这不…...

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 &#xff08;便携式文件格式&#xff0c;Portable Document Format&#xff09;是由Adobe Systems在1993年用于文件交换所发展出的文件格式。 PDF主要由三项技术组成&#xff1a;衍生自PostScript&#xff1b;字型嵌入系统&#xff1b;资料压缩及传…...

19c补丁后oracle属主变化,导致不能识别磁盘组

补丁后服务器重启&#xff0c;数据库再次无法启动 ORA01017: invalid username/password; logon denied Oracle 19c 在打上 19.23 或以上补丁版本后&#xff0c;存在与用户组权限相关的问题。具体表现为&#xff0c;Oracle 实例的运行用户&#xff08;oracle&#xff09;和集…...

CVPR 2025 MIMO: 支持视觉指代和像素grounding 的医学视觉语言模型

CVPR 2025 | MIMO&#xff1a;支持视觉指代和像素对齐的医学视觉语言模型 论文信息 标题&#xff1a;MIMO: A medical vision language model with visual referring multimodal input and pixel grounding multimodal output作者&#xff1a;Yanyuan Chen, Dexuan Xu, Yu Hu…...

Spring Cloud Gateway 中自定义验证码接口返回 404 的排查与解决

Spring Cloud Gateway 中自定义验证码接口返回 404 的排查与解决 问题背景 在一个基于 Spring Cloud Gateway WebFlux 构建的微服务项目中&#xff0c;新增了一个本地验证码接口 /code&#xff0c;使用函数式路由&#xff08;RouterFunction&#xff09;和 Hutool 的 Circle…...

【生成模型】视频生成论文调研

工作清单 上游应用方向&#xff1a;控制、速度、时长、高动态、多主体驱动 类型工作基础模型WAN / WAN-VACE / HunyuanVideo控制条件轨迹控制ATI~镜头控制ReCamMaster~多主体驱动Phantom~音频驱动Let Them Talk: Audio-Driven Multi-Person Conversational Video Generation速…...

QT3D学习笔记——圆台、圆锥

类名作用Qt3DWindow3D渲染窗口容器QEntity场景中的实体&#xff08;对象或容器&#xff09;QCamera控制观察视角QPointLight点光源QConeMesh圆锥几何网格QTransform控制实体的位置/旋转/缩放QPhongMaterialPhong光照材质&#xff08;定义颜色、反光等&#xff09;QFirstPersonC…...

音视频——I2S 协议详解

I2S 协议详解 I2S (Inter-IC Sound) 协议是一种串行总线协议&#xff0c;专门用于在数字音频设备之间传输数字音频数据。它由飞利浦&#xff08;Philips&#xff09;公司开发&#xff0c;以其简单、高效和广泛的兼容性而闻名。 1. 信号线 I2S 协议通常使用三根或四根信号线&a…...

Kubernetes 网络模型深度解析:Pod IP 与 Service 的负载均衡机制,Service到底是什么?

Pod IP 的本质与特性 Pod IP 的定位 纯端点地址&#xff1a;Pod IP 是分配给 Pod 网络命名空间的真实 IP 地址&#xff08;如 10.244.1.2&#xff09;无特殊名称&#xff1a;在 Kubernetes 中&#xff0c;它通常被称为 “Pod IP” 或 “容器 IP”生命周期&#xff1a;与 Pod …...

Vue 模板语句的数据来源

&#x1f9e9; Vue 模板语句的数据来源&#xff1a;全方位解析 Vue 模板&#xff08;<template> 部分&#xff09;中的表达式、指令绑定&#xff08;如 v-bind, v-on&#xff09;和插值&#xff08;{{ }}&#xff09;都在一个特定的作用域内求值。这个作用域由当前 组件…...

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

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

MFE(微前端) Module Federation:Webpack.config.js文件中每个属性的含义解释

以Module Federation 插件详为例&#xff0c;Webpack.config.js它可能的配置和含义如下&#xff1a; 前言 Module Federation 的Webpack.config.js核心配置包括&#xff1a; name filename&#xff08;定义应用标识&#xff09; remotes&#xff08;引用远程模块&#xff0…...