释放锁流程源码剖析

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时,成本很高。
        当前在唤醒后继节点时,如果是单向链表也会出问题,因为节点插入方式的问题,导致只能单向的去找有效节点去唤醒,从而造成很多次无效的遍历操作,如果是双向链表就可以解决这个问题。

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

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

相关文章

TC397 EB MCAL开发从0开始系列 之 [15.0] Fee配置说明 -理论详解

一、Fls模块详解1. FEE驱动工作模式:1.1 双扇区&QS1.2 单双扇区1.3 单QS2. Fls配置相关3. 初始化FEE驱动程序4. FEE operation5. Configuration of QS blocks6. Key points to consider6.1 FEE和FLS依赖6.2 GC介绍6.3 写块接近GC阈值6.4 FEE_E_GC_TRIG DEM6.5 Fee_Read和Fe…

EtherCAT从站EEPROM分类附加信息详解:RXPDO(输入过程数据对象)

0 工具准备 1.EtherCAT从站EEPROM数据(本文使用DE3E-556步进电机驱动器)1 分类附加信息——RXPDO(输入过程数据对象) 1.1 分类附加信息规范 在EEPROM字64开始的区域存储的是分类附加信息,这里存储了包括设备信息、SM配置、FMMU配置在内的诸多信息。每个信息在一段连续的…

python实战—核心基础1(高考倒计时)lv1

目录 一、核心代码解释 二、代码 三、运行截图 一、核心代码解释 1、datetime模块 Python有一个名为datetime的模块&#xff0c;用于处理日期和时间。 datetime模块中定义的一个类是datetime类。 可以使用now()方法创建一个包含当前本地日期和时间的datetime对象。 impo…

藏头诗(C语言)

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

Milvus Standalone安装

使用Docker Compose安装 Milvus standalone&#xff08;即单机版&#xff09;&#xff0c;进行一个快速milvus的体验。 前提条件&#xff1a; 1.系统可以使用centos 2.系统已经安装docker和docker-compose 3.milvus版本这里选择2.3.1 由于milvus依赖etcd和minio&#xff0c…

公司电脑文件透明加密、防泄密管理软件系统

天锐绿盾数据透明加密系统是一款采用驱动层透明加密技术实现电子文件安全加密的防护产品&#xff0c;可以对企业电子文件的存储、访问、传播和处理过程实施全方位保护。该系统遵循基于文件生命周期安全防护的思想&#xff0c;集成了密码学、访问控制和审计跟踪等技术手段&#…

MySQL优化-查询优化

MySQL查询优化是指通过调整查询语句、优化表结构、使用索引等方式&#xff0c;提高查询性能的过程。以下是MySQL查询优化的几种方法&#xff1a; 1. 尽量避免使用SELECT* SELECT *会查询表中的所有列&#xff0c;包括不需要的列&#xff0c;这会消耗大量的计算资源和时间。而…

PTA目录树

在ZIP归档文件中&#xff0c;保留着所有压缩文件和目录的相对路径和名称。当使用WinZIP等GUI软件打开ZIP归档文件时&#xff0c;可以从这些信息中重建目录的树状结构。请编写程序实现目录的树状结构的重建工作。 输入格式: 输入首先给出正整数N&#xff08;≤104&#xff09;…

【开源】基于Vue.js的计算机机房作业管理系统的设计和实现

项目编号&#xff1a; S 017 &#xff0c;文末获取源码。 \color{red}{项目编号&#xff1a;S017&#xff0c;文末获取源码。} 项目编号&#xff1a;S017&#xff0c;文末获取源码。 目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 登录注册模块2.2 课程管理模块2.3 课…

某app c++层3处魔改md5详解

hello everybody,本期是安卓逆向so层魔改md5教学,干货满满,可以细细品味,重点介绍的是so层魔改md5的处理. 常见的魔改md5有: 1:明文加密前处理 2:改初始化魔数 3:改k表中的值 4:改循环左移的次数 本期遇到的是124.且循环左移的次数是动态的,需要前面的加密结果处理生成 目录…

crmchat安装搭建教程文档 bug问题调试

一、安装PHP插件&#xff1a;fileinfo、redis、swoole4。 二、删除PHP对应版本中的 proc_open禁用函数。 一、设置网站运行目录public&#xff0c; 二、设置PHP版本选择纯静态。 三、可选项如有需求则开启SSL,配置SSL证书&#xff0c;开启强制https域名。 四、添加反向代理。 …

Docker发布简单springboot项目

Docker发布简单springboot项目 在IDEA工具中直接编写Dockerfile文件 FROM java:8COPY *.jar /app.jarCMD ["--server.prot 8080"]EXPOSE 8080ENTRYPOINT ["java", "-jar", "/app.jar"]将项目打包成对应的jar包&#xff0c;将Dockerf…

MySQL 的执行原理(三)

5.4. InnoDB 中的统计数据 我们前边唠叨查询成本的时候经常用到一些统计数据&#xff0c;比如通过 SHOW TABLE STATUS 可以看到关于表的统计数据&#xff0c;通过 SHOW INDEX 可以看到关于索引 的统计数据&#xff0c;那么这些统计数据是怎么来的呢&#xff1f;它们是以什么方…

git下载安装配置及Git在Gitee上拉取和上传代码教程

一、Git下载安装和配置 Git是一个分布式版本控制系统&#xff0c;用于跟踪文件的变化并协作开发。以下是安装和配置Git的简单步骤&#xff1a; 安装Git 下载Git安装程序&#xff1a;Git下载地址。 运行安装程序&#xff0c;按照提示进行安装。 在安装过程中&#xff0c;选择…

Notepad++ 和正则表达式 只保留自己想要的内容

一、需求 如下文本&#xff0c;三段相同结构的数据&#xff0c;想要获取每段结构中‘重复的Ids ’后面的数字 2023-10-26 18:49:49 重复的Ids 26443,26575 要删除的Ids 4174,4199,4200,55502023-10-26 18:49:49 重复的Ids 26436,26443,26575 要删除的Ids 4166,4199,4200,5550…

特殊文件(XML文件)

一&#xff0c;XML文件概括 二&#xff0c;案例 <?xml version"1.0" encoding"UTF-8" ?> <!--注释&#xff1a;以上抬头声明必须写在第一不然报错--> <users><user id"1"><uame>张无忌</uame><性别&g…

bson数据通过BulkOperations批量入库mongo表报重复ID处理

bson数据批量入库mongo表报重复ID处理 一、需求 需要将一批bson格式数据批量入库指定mongo表&#xff0c;并且该表已存在数据&#xff0c;批量入库的数据可能和表中已有数据重复。 二、批量入库时的问题 采用MongoTemplate 原生的BulkOperations批量操作API upsert方法进行…

STM32:OLED屏幕开发

一、OLED原理 所谓的屏幕就是由一个个小灯组成&#xff0c;每个小灯称之为一个像素。只要在屏幕上有选择地点亮一部分小灯&#xff0c;就可以显示我们想要的图案。所谓下分辨率就是屏幕上的小灯数量。常见单片机中常见的屏幕分辨率常见的就是128(列长)*64(行高)。如果每个小灯都…

【云栖 2023】张治国:MaxCompute 架构升级及开放性解读

云布道师 本文根据 2023 云栖大会演讲实录整理而成&#xff0c;演讲信息如下 演讲人&#xff1a;张治国|阿里云智能计算平台研究员、阿里云 MaxCompute 负责人 演讲主题&#xff1a;MaxCompute架构升级及开放性解读 活动&#xff1a;2023云栖大会 MaxCompute 发展经历了三个阶…

发币成功,记录一下~

N年前就听说了这样一种说法——“一个熟练的区块链工程师&#xff0c;10分钟就可以发出一个新的币” 以前仅仅是有这么一个认识&#xff0c;但当时并不特别关注这个领域。 最近系统性学习中&#xff0c;今天尝试发币成功啦&#xff0c;记录一下&#xff5e; 发在 Sepolia Tes…