互斥锁的实现细节

2023-11-13

***首先,一个互斥锁要实现什么功能?***

一个互斥锁需要有阻塞和唤醒功能,实现阻塞和唤醒功能需要哪些要素?
①需要有一个标记锁状态的state变量。
②需要记录哪个线程持有了锁。
③需要有一个队列维护所有的线程。
另外,state和队列中为了实现线程安全都用到了CAS。
有了以上三个条件,可以实现对线程的阻塞和唤醒。

***那么,Java中是如何实现一把互斥锁的?***

首先,因为所有的锁基本都要实现重入功能,那么Java中的互斥锁由ReentrantLock类来定义,也就是可重入锁,也就是说ReentrantLock类中的锁是互斥锁。(虽然ReentrantLock很重要,但是ReentrantLock里面并没有代码逻辑,ReentrantLock里面的具体实现就是在NonfairSync和FairSync中,ReentrantLock就是构造方法里面也是 new 一个 NonfairSync或者FairSync实例,还有lock和unlock是调用的sync.lock 和 sync.release,而sync中的lock是抽象方法,又由NonfairSync和FairSync分别实现,而sync.release则是sync继承的AbstractQueuedSynchronized中的方法,所以unlock并没有公平和非公平两种实现 )。
先介绍一下ReentrantLock中的继承结构,ReentrantLock实现了Lock接口,实现了Lock接口中的lock() , lockInterruptibly() , unlock() , tryLock() , tryLock(long time, TimeUnit unit) , Condition newCondition();其中 lockInterruptibly() 和 tryLock(long time, TimeUnit unit) 都会抛出InterruptedException。而且 ReentrantLock 引用了 Sync 抽象内部类,Sync 又分别被 NonfairSync 和 FairSync 实现,Sync还继承了AbstractQueuedSynchroinzer 队列同步器,AbstractQueuedSynchronized又继承了AbstractOwnerSynchronize。
介绍完ReentrantLock我们继续介绍,ReentrantLock是如何实现互斥锁的?

1.State,state定义在AbstractQueuedSynchronized中,用来记录线程重入了多少次这个锁。

2.记录哪个线程持有了锁,在AbstractOwnerSynchronized中定义了Thread类型的 exclusiveOwnerThread(独占拥有者线程)来记录锁的拥有线程。

3.队列,在AbstractQueuedSynchronized中主要就是定义了一个用双向链表实现的队列,用来存储线程,实现线程管理。

如何通过这些条件实现阻塞和唤醒?(涉及公平和非公平的)

阻塞是在什么情况下发生的?线程抢锁,抢锁成功访问资源,抢锁失败阻塞。唤醒?从阻塞队列中被唤醒后尝试拿锁,访问资源,没拿到锁会再次被阻塞(两种唤醒方式unpark(Thread t)、t.interrupt()两种方式都需要其他的线程执行)。 

什么情况下线程抢锁?多个线程都调用了lock方法,都在尝试给自己上锁,尝试之后没有抢到会怎么样?只能阻塞。下面我们来看NonfairSync和FairSync对最终对lock方法的不同实现:

    static final class NonfairSync extends Sync {
        private static final long serialVersionUID = 7316153563782823691L;

        /**
         * Performs lock.  Try immediate barge, backing up to normal
         * acquire on failure.
         */
        final void lock() {
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);
        }

        protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
        }
    }
static final class FairSync extends Sync {
        private static final long serialVersionUID = -3000897897090466540L;

        final void lock() {
            acquire(1);
        }

        /**
         * Fair version of tryAcquire.  Don't grant access unless
         * recursive call or no waiters or is first.
         */
        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;
        }
    }

我们可以看到,不管是NonfairSync或者FairSync中的lock方法最后都要执行acquire(int arg)方法,acquire翻译过来就是获取的意思,就是获取锁,acquire是继承自AbstractQueuedSynchronized的方法,代码如下:

public final void acquire(int arg) {
    if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
}

我们其实可以从方法名中看出acquire方法内部是获取锁的逻辑,但是非公平锁很不讲道理的在还没有进入acquire方法正式抢锁之前就已经尝试抢过一次锁了。进入acquire方法中,首先进入一个tryAcquire()尝试抢锁方法,这个方法在AbstractQueuedSynchronized中有定义,但是被NonfairSync和FairSync分别重写了,其中NonfairSync的tryAcquire方法中又返回了Sync中的nonfairTryAcquire方法,表明,NonfairSync类中的tryAcquire方法的主体逻辑写在了Sync类中,FairSync类中的tryAcquire方法中的逻辑还是正常的在自己的类中。

回来,我们继续说tryAcquire()方法有一个返回值,就是获取到锁返回true,没有获取到锁返回false,后面的acquireQueued方法是一个阻塞逻辑。if里面的逻辑就是,如果抢锁失败,返回false,执行acquireQueued()方法阻塞当前线程,如果抢锁成功就已经完成了目标不执行后面的阻塞逻辑。那么,tryAcquire(int arg)方法中arg总是1,意味着每抢锁成功一次加一,tryAcquire中的总体逻辑是,如果state值为0,那么在非公平锁中直接尝试设置state为1,设置成功就将标记独占者线程的Thread类型属性改为当前线程,完成抢锁,成功获得标记,没有被阻塞。如果state值不为0,那么就使state加1并且更新state的值,更新state值之前如果发现加一之后state小于0,则抛出锁的最大数量已经被超出了错误,一般不会发生,因为上限有20多亿(超过上限加一,变为负数,计算机底层二进制计算原理,应该是首位符号位变了);而在公平锁中,在state===0 时需要判断当前线程是否在阻塞队列头,如果当前线程在队列头,或者队列为空,则该线程可以抢锁。state不为零时和NonfairSync一样判断一下当前线程是被标记的独占者线程后,如果是就可以直接操作state,重入时不需要判断阻塞队列的情况。

获取锁失败之后,线程进入acquireQueued方法的逻辑中,acquireQueued方法中有一个addWaiter方法先将线程加入阻塞队列的最后一个节点,加入阻塞队列后并不会发生阻塞,只是记录了将线程记录到队列里面,等到线程进入acquireQueued方法才会阻塞。acquireQueued方法的内部的主体逻辑就是一个for死循环的内容,循环中首先判断线程所在节点是不是队列中的第一个节点,方法里用的是判断当前节点的前驱节点是不是head节点,如果是,则表明该节点位于队列第一个,则尝试获得锁,如果获得锁成功,就将该节点设置为头结点(也就是出队列,使head指针右移,并且是节点的Thread属性设置为null,也就是head还是指向了一个空节点),然后把p.next设置为null便于GC,最后返回一个布尔值判断该线程是否被中断过。如果线程没有获取到锁,或者根本不在队列第一位,那么直接跳到一个if()逻辑里,里面有两个方法,shouldParkAfterFailedAcquire(...)判断获取锁失败后应不应该阻塞,如果应该阻塞,则执行parkAndCheckInterrupt()方法,parkAndCheckInterrupt()方法里的逻辑也很简单,直接调用LockSupport.park(this)阻塞该线程,然后返回一个Thread.interrupted(),判断线程是否被中断过,如果被中断过,返回true,进入if下面的逻辑,设置中断标志值为true,表示该线程被中断过,acquireQueued方法就会返回true,就会执行selfInterrupt方法中断自己,自己的状态就会编程已被中断过状态;如果该线程没有被中断过,则Thread.interrupted()返回false,不执行if下面的逻辑中断标志值就为false,那么acquireQueued方法就会返回false,不会使用selfInterrupt()方法,也就不会把自己设置为已被中断过状态。之所以要自己中断自己是因为,线程被阻塞期间即使被中断了,也没有完成把自己的状态设置为已被中断过的状态。

以上是对lock()方法的介绍,下面我们来看看unlock() 方法的实现,lock()方法是获取锁,谁获取到锁谁访问资源,unlock()方法是释放锁,只有拥有锁的线程才能释放锁,否则会产生IllegalMonitorStateException;unlock()方法中返回了sync.release(1),而sync.release(1)是从AbstractQueuedSynchronized类中继承的方法。release(int arg)方法主要逻辑由两个方法执行,一个是tryRelease(int arg)方法和unparkSuccessor(Node node)方法,tryRelease(int arg)尝试释放锁,每调用一次state减一,如果state==0,那么使用set ExclusiveOwnerThread(Thread thread)方法,将exclusiveOwnerThread设置为null,表示没有线程持有该锁。释放锁成功则返回true,就绪执行if中的逻辑,里面逻辑大致为如果队列中有节点的话,就调用unparkSuccessor(Node node)唤醒该方法。也就是唤醒后继节点,使其尝试获取锁。

再来看一个方法lockInterruptibly()实现分析,其实和lock的几乎一样,唯一的区别就是可被interrupt()方法唤醒,怎么实现的呢?就是在parkAndCheckInterrupt()方法那里当Thread.interrupted()返回为true进入if下的逻辑时,把lock时设置的中断标志变量,改为抛出一个异常,那么就实现了可被中断的作用。

最后是一个tryLock方法,ReentrantLock中的tryLock方法返回一个sync.nonfairTryAcquire(1)方法,sync.nonfairTryAcquire(int arg)会尝试获取锁,如果没有获取锁成功,会返回一个false,获取成功就会返回true,sync.nonfairTryAcquire(int arg)中只尝试获取锁一次,并不循环,然会返回true或false,再由tryLock方法返回这个值。需要注意的是非公平锁的lock其实也用了这个方法获取锁,但是如果返回false,lock中的代码会使的线程继续进入acquireQueued方法阻塞。

 

 

 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

互斥锁的实现细节 的相关文章

  • sql sever2008 R2 检测到索引可能已损坏。请运行 DBCC CHECKDB。

    1 设置成单用户状态 USE MASTER ALTER DATABASE DBNAME SET SINGLE USER GO DBNAME为修复的数据库名 2 执行修复语句 检查和修复数据库及索引 dbcc checkdb DBNAME R
  • 【pip】彻底解决 module ‘tensorflow‘ has no attribute ‘random_normal‘

    翻译 tensorflow显示没有random normal模块 解决 将代码中的 tf random normal 用tf random normal代替 区分 与
  • [leetcode]python3 算法攻略-回文链表

    请判断一个链表是否为回文链表 方案一 指针法 class Solution def isPalindrome self head 判断一个链表是否是回文的 很自然的想法就是两个指针 一个指针从前往后走 一个指针从后往前走 判断元素值是否相同
  • mysql读写分离与监控的使用(proxysql)

    os rhel 7 3 mysql 5 7 proxysql 1 4 15 1 ip 规划如下 172 25 11 1 node1 proxysql 172 25 11 2 node2 mysql master 172 25 11 3 no
  • 对于解决Visual Studio中scanf函数报错的原因及解决方法

    对于C语言初学者 可能会用到devC 或者是visual studio软件 我本人是比较推荐visual studio软件的 毕竟这个软件使用起来功能比devc 软件功能更多 而初学者在使用visual studio软件时会发现在使用初始的
  • Unity-委托2种常用使用场景总结

    委托使用场景1 调用委托 可以分发多个方法出去 举例 定义多个通知不同人的信息 例如经理 员工 客户 可以针对性的制定不同的通知 调用委托 可以一次性的群发给他们 委托使用场景2 方法的参数是个方法 例如按钮方法 参数是一个点击事件的方法
  • 麦克灵敏度调整

    1 先看MIC电路连接 这是个差分输入的例子 MICP2和MICN2是一对差分信号 经过C156的滤波 输入到MIC两端 MIC两引脚分别是到地和供电 上图的R177参数就关系到MIC输入的灵敏度 2 电阻R177影响灵敏度分析 MICBI
  • C++中函数返回引用

    1 返回引用和不返回引用的区别 下面两个代码是在类中的成员函数 而m data 变量为类的私有成员变量 int at return m data int at return m data 上面两个函数 第一个返回值是int的引用int 第二
  • Log Structured Merge Trees(LSM) 原理

    Log Structured Merge Trees LSM 原理 十年前 谷歌发表了 BigTable 的论文 论文中很多很酷的方面之一就是它所使用的文件组织方式 这个方法更一般的名字叫 Log Structured Merge Tree
  • 【数据结构】唯一确定一个二叉树的方法

    唯一确定一棵二叉树的方法 在了解以何种方式能唯一确定一棵二叉树之前 需要先认识树的遍历方式有哪几种 树的遍历方式 先序遍历 后序遍历 层序遍历 二叉树的遍历方式 先序遍历 中序遍历 后序遍历 层序遍历 确定的方式 那么如何唯一确定一棵二叉树
  • “0xc000007b无法正常启动”解决方案汇总

    今天在运行一个Opengl项目时总是一直报 0xc000007b无法正常启动 的错误 于是百度了一些解决方案 当然这些解决方案是针对不同错误原因提出来的 所以如果读者也遇到同样的错误可以一一尝试 或者首先分析原因再选择解决方案 现将这个问题
  • 如何在windows编译ffmpeg

    在Windows上编译FFmpeg可以使用MSYS2或Cygwin来配置编译环境 然后使用MinGW或Visual Studio等工具进行编译 下面是使用MSYS2和MinGW的步骤 下载并安装MSYS2 https www msys2 o
  • qt中关于按钮的click()函数卓见

    概述 按钮有一个基类QAbstractButton 这个类中有一个函数click j几乎所有的函数都继承了这个类QAbstractButton 同时这个函数click 是一个公有的槽函数 也就是意味着所有的子类都可以调用这个函数 而这个cl
  • java基础之用switch判断学生成绩等级

    package 流程控制 练习 1 用switch语句完成输入学生的考试成绩判断成绩等级 2 等级 优秀 90 100 良好 80 90 中等 70 80 及格 60 70 不及格 0 60 3 成绩必须在 0 100 之间 import
  • Vue中filters过滤器的封装

    目录 Vue中的filters过滤器是什么 Vue中为什么要封装filters过滤器 封装准备开始 如何封装一个全局的filters过滤器 Vue中的filters过滤器是什么 在vue中过滤器实质上是对文本进行格式化 在渲染前对数据进行处
  • joblib嵌套式并行运行方式

    20220811 0 引言 在进行机器学习的相关实验中 当使用sklearn的时候 通常可以通过n jobs 1这个参数实现某些算法的并行化 例如集成学习的方法 或者是参数搜索的函数 通过查看相关的文档 或者直接去看这个代码 可以发现skl
  • 您的设备不支持googleplay服务_[GoPro] APP不支持Google Play服务解决办法

    很多安卓手机用户在初次使用GoPro APP的时候有可能会遇到下图设备不支持Google Play服务的提示 这是因为国内手机默认设置没有Google三件套 接下来我们介绍两种解决办法 一适用除部分华为新机型以外的安卓机 首先直接网页搜索或
  • 【论文笔记】nnU-Net: a self-configuring method for deep learning-based biomedical image segmentation

    nnU Net 一种基于深度学习的自配置生物医学图像分割方法 Results nnU Net的自动配置基于将领域知识提取成三个参数组 固定的 基于规则的和经验的参数 收集不需要在数据集之间进行调整的设计决策 并确定稳健的通用配置 fixed
  • mysql一行逗号分割的数据分解为多行

    在 MySQL 中 你可以使用函数 REPLACE 和 SUBSTRING INDEX 来将一行逗号分隔的数据分解为多行 例如 假设你有一个表 其中包含一列 items 该列包含逗号分隔的字符串 如下所示 id items 1 item1
  • 机器学习笔记 - 【机器学习案例】在表格数据上应用高斯混合模型GMM和网格搜索GridSearchCV提高分类精度

    1 需求及数据集说明 这是一项二分类任务 评估的是分类准确性 正确预测的标签百分比 训练集有1000个样本 测试集有9000个样本 你的预测应该是一个9000 x 1的向量 您还需要一个Id列 1到9000 并且应该包括一个标题 格式如下所

随机推荐

  • 【学习笔记】数据存储的顺序与对齐_计算机原理

    学习笔记 数据存储的顺序与对齐 计算机原理 开了个新坑 做一个计算机原理的读书笔记 自用 仅做分享 数据存储的顺序 介绍了机器端模式 数据存储的对齐 这里介绍的是 数据结构的存储方式是根据给定的字节和自己本身的字节做对齐的 比如 4 4 4
  • 浅谈SSM框架下实现简单登录界面

    SSM框架搭建结果 Login jsp
  • C:\Python27\python.exe: can't open file '2.py': [Errno 2] No such file or directory

    最近开始学习了 笨方法学python 在练习的第一个程序就出现了如下错误 C Python27 python exe can t open file 2 py Errno 2 No such file or directory 刚开始自己找
  • 学习日记——DAC实验(2020.2.6)

    区分ADC和DAC ADC 输入 模拟量转到数字量输入 DAC 输出 数字量转到模拟量输出 概念 数模转换器 又称D A转换器 简称DAC 它是把数字量转变成模拟的器件 D A转换器基本上由4个部分组成 即权电阻网络 运算放大器 基准电源和
  • Centos MySql安装,保姆级安装教程

    Centos MySql安装 保姆级安装教程 1 删除原有的mariadb 不然mysql装不进去 查询MAriaDB命令 rpm qa grep mariadb 删除 rpm e nodeps mariadb libs 5 5 60 1
  • pip与conda有什么区别,常用命令有哪些?

    文章目录 pip与conda有什么区别 常用命令有哪些 1 pip与conda 2 pip常用命令 3 conda常用命令 pip与conda有什么区别 常用命令有哪些 1 pip与conda pip是Python 包管理工具 该工具提供了
  • 关于Linux/Ubuntu重置用户名密码

    Step1 重启 往死里按Esc或等启动后嗯Enter Step2 成功后会进入如下界面 这一步后如果你已经知道root密码 请直接跳到Step8 Step3 在菜单Ubuntu这摁 e 进入编辑模式 对标出行 做出如下修改 Step4 根
  • 零信任架构简介

    2021 年被誉为网络安全元年 种种因素极大的驱动了零信任成为安全新风口 零信任也无疑成为了整个安全圈包括网络安全领域最热门的词汇之一 什么是零信任 零信任既不是单一的产品 也不是单一的技术 它是一种安全理念以及安全架构 核心原则是持续验证
  • SV中program & module

    相同之处 1 和module相同 program也可以定义0个或多个输入 输出 双向端口 2 一个program块内部可以包含0个或多个initial块 generate块 specparam语句 连续赋值语句 并发断言 timeunit声
  • GPS 的PPS

    校准RTC时间的方法 首先需要一个准确的外部信号 比如GPS来的秒信号 或者其它很准确的信号 然后通过定时器来测量RTC的晶振误差 然后再对该误差进行校准 面接收机GPS的秒脉冲精度 也就是相邻两个秒脉冲上升沿的间隔精度能到100ns 授时
  • HTML+CSS+JS制作【飞机大战】小游戏(键盘版和鼠标版)

    文章目录 一 效果演示 设计思路 二 鼠标版飞机大战代码展示 1 HTML结构代码 2 CSS样式代码 3 JavaScript代码 js js文件 plane js文件 三 键盘版飞机大战代码展示 1 HTML结构代码 2 CSS样式代码
  • eclipse下maven打包失败(Please ensure you are using JDK 1.4 or above and not ......

    在eclipse下用maven编译时 可能会失败 报出以下提示 ERROR Unable to locate the Javac Compiler in ERROR C Program Files Java jre1 8 0 72 lib
  • 【ABviewer从零开始教学查看器篇①】3D查看器和3D剖面板

    ABViewer是一款高质量 高效率 低成本的多功能设计及工程文档管理工具 能为您提供全面的专业的浏览及编辑功能 同时支持30多种光栅和矢量图形格式 在小编看来 ABViewer是一款非常简单且实用的CAD文档查看与编辑器 对于使用小白可能
  • 华为云计算相关知识点

    云计算离不开网络基础设施 云计算中的网络分为不同的平面 管理平面 负责整个系统的监控 操作维护 系统配置 系统加载 告警上报 和虚拟机管理 创建 删除虚拟机 虚拟机调度 等 存储平面 主要为存储系统提供通信平面 并未虚拟机提供存储资源 用于
  • 你的数据隐私值多少钱?也许已有答案了

    全文共6032字 预计学习时长12分钟 图片来源 Timo Lenzen 对于一些大型科技公司来说 这一年侵犯用户隐私付出的代价变高了 未来还会更高吗 今年7月 脸书在受到有关泄露数亿用户数据隐私的指控后 同意缴纳50亿美元的罚金 同一周内
  • 【TOOLS】Python 3利用SMTP进行邮件Email自主发送

    作者 Che Hongshu 来源 AI蜗牛车 ID AI For Car 一 前言 利用Python进行邮件的发送 这个功能自我感觉主要应用于检测或者报告之类 我两次运用这个功能 第一次用在主要发送实时的数据给一个邮箱 第二次用是检测挂在
  • VS调试:函数断点与数据断点

    断点 是Debug过程中最常用的功能 关于断点VS还有很多高级功能 本文使用的是VS2017 介绍函数断点与数据断点的使用场景以及使用方法 1 普通断点 普通断点是最常接触的断点 VS中 在代码行左边栏灰色区域点击 或者把光标放在某代码行按
  • 使用Python编写Maya脚本插件批量导入Obj文件

    最近开发中遇到需要使用Python语言编写Maya脚本 要求使用脚本选择某一磁盘路径 脚本根据路径自动导入路径与子目录下的所有OBJ文件 并重命名它们 在Maya中 有自带的脚本编辑器供我们使用 这使得我们编写代码非常轻松 打开脚本编辑器
  • Installation did not succeed. The application could not be installed: INSTALL_FAILED_USER_RESTRICTED

    当我们第一次在我们的手机上 也就是物理设备上 运行我们的写好的安卓应用程序时可能会报以下错误 Session app Installation did not succeed The application could not be ins
  • 互斥锁的实现细节

    首先 一个互斥锁要实现什么功能 一个互斥锁需要有阻塞和唤醒功能 实现阻塞和唤醒功能需要哪些要素 需要有一个标记锁状态的state变量 需要记录哪个线程持有了锁 需要有一个队列维护所有的线程 另外 state和队列中为了实现线程安全都用到了C