synchronized底层是怎么通过monitor进行加锁的?

一、monitor是什么

monitor叫做对象监视器、也叫作监视器锁,JVM规定了每一个java对象都有一个monitor对象与之对应,这monitor是JVM帮我们创建的,在底层使用C++实现的

ObjectMonitor() {_header;_count ; // 非常重要,表示锁计数器,_count = 0表示还没人加锁,_count > 0 表示加锁的次数_waiters;_recursions;_owner; // 非常重要,指向加锁成功的线程,_owner = null 时候表示没人加锁_waitset; // wait线程的集合,在synchorized代码块中调用wait()方法的线程会被加入到此集合中沉睡,等待别人叫醒它_waitsetLock;_responsiable;_succ;_cxq;_freenext;_entrylist; // 非常重要,等待队列,加锁失败的线程会被加入到这个等待队列中,等待再次争抢锁_spinFreq; // 获取锁之前的自旋的次数_spinclock; // 获取之前每次锁自旋的时间ownerIsThread;}
二、monitor对象的关键属性

_count : 这个属性非常重要,直接表示有没有被加锁,如果没被线程加锁则 _count=0,如果 _count大于0则说明被加锁了

_owner:这个属性也非常重要,直接指向加锁的线程,比如线程A获取锁成功了,则 _owner = 线程A;当 _owner = null的时候表示没线程加锁

_waitset:当持有锁的线程调用wait() 方法的时候,那个线程就会释放锁,然后线程被加入到monitor的waitset集合中等待,然后线程就会被挂起。只有有别的线程调用notify将它唤醒

_entrylist:这个就是等待队列,当线程加锁失败的时候被block住,然后线程会被加入到这个entrylist队列中,等待获取锁。

_spinFreq:获取锁失败前自旋的次数;JDK1.6之后对synchronized进行优化;原先JDK1.6以前,只要线程获取锁失败,线程立马被挂起,线程醒来的时候再去竞争锁,这样会导致频繁的上下文切换,性能太差了。JDK1.6后优化了这个问题,就是线程获取锁失败之后不会被立马挂起而是每个一段时间都会重试去争抢一次,这个 _spinFreq就是最大的重试次数也就是自旋的次数,如果超过了这个次数抢不到,那线程只能沉睡了。

_spinClock:上面说获取锁失败每隔一段时间都会重试一次,这个属性就是自旋间隔的时间周期,比如50ms,那么就是每隔50ms就尝试一次获取锁。

三、那么这些属性是怎么进行加锁的呢?

(1)首先,没有线程对monitor进行加锁的时候是这样的:

_count = 0 表示加锁次数是0,也就是没线程加锁; _owner 指向null,也就是没线程加锁

(2)然后,这个时候线程A、线程B来竞争加锁了,如下图所示:

(3)线程A竞争到锁,将 _count 修改为1,表示加锁次数为1,将_owner = 线程A,也就是指向自己,表示线程A获取到了锁。

(4)那反过来推测,释放锁的时候是不是将_count 设置为 0 , 将 _owner 设置为 null 就 OK了?对的

四、那monitor的其他属性是用来干啥的呢?

上面说过 _spinFreq是等待锁期间自旋的次数_spinclock是自旋的周期也就是每次自旋多久时间、 _entrylist这个就是自旋次数用完了还没获取锁,只能放到 _entrylist等待队列挂起了。如下图:

(1)首先线程B获取锁的时候发现monitor已经被线程A加锁

(2)然后monitor里面记录的 _spinFreqspinclock 信息告诉线程B,你可以每隔50ms来尝试加锁一次,总共可以尝试10次

(3)如果线程B10次尝试加锁期间,获取锁成功了,那线程B将 _count 设置为 1_owner 指向自己表示自己获取锁成功了

(4)如果10次尝试获取锁此时都用完了,那没辙了,它只能放到等待队列里面先睡觉去了,也就是线程B被挂起了。

五、获取锁失败后的自旋操作

为啥线程B请求失败之后不直接进入队列挂起?而是要自旋之后再次尝试获取锁?为啥不是一直自旋然后尝试获取锁,而是要设置一个最大尝试次数?

这个啊,其实跟jvm获取monitor锁的优化有关,这么做有什么好处呢?

(1)首先跟你说下,线程挂起之后唤醒的代价很大,底层涉及到上下文切换,用户态和内核态的切换,我打个比方可能最少耗时3000ms这样,这只是打个比方哈

(2)线程A获取了锁,这个时候线程B获取失败。按照上面自旋的数据 _spinclock = 50ms(每次自旋50ms), _spinFreq = 10(最多10次自旋)

(3)假如线程A使用的时间很短,比如只使用150ms的时间;那么线程B自旋3次后就能获取到锁了,也就花费了150ms左右的时间,相比于挂起之后唤醒最少花费3000ms的时间,是不是大大减少了等待时间啊......,这也就提高了性能了。

(4)如果不设置自旋的次数限制,而是让它一直自旋。假如线程A这哥们耗时特别的久,比如它可能在里面搞一下磁盘IO或者网络的操作,花了5000ms!!。

线程B可不能在那一直自旋着等着它吧,毕竟自旋可是一直使用CPU不释放CPU资源的,CPU这时也在等着不能干别的事,这可是浪费资源啊,所以啊自旋次数也是要有限制的,不能一直等着,否则CPU的利用率大大被降低了。

所以在10次自旋之后,也就是500ms之后,还获取失败,那就把自己挂起,释放CPU资源咯

六、monitor的wait和notify

说起monitor里面的waitset,上面讲的就是一个集合

必须是当线程获取锁之后,才能调用wait()方法,然后此时释放锁,将_count恢复为0,将_owner指向 null,然后将自己加入到waitset集合中等待别人调用notify或者notifyAll将其中waitset的线程唤醒。

那notify和notifyAll有啥区别啊?

简单说就是notify就是从waitset随机挑一个线程来唤醒,只唤醒一个。notifyAll这方法就是将waitset中所有等着的线程全部唤醒了。

假如说现在有个场景是这样的:

线程A执行如下代码:

synchronized(this) {if (某个条件) {wait();    }    
}

线程B执行如下代码:

synchronized(this) {// 某些业务逻辑......notify();
}

下面画个图来说一下:

(1)首先啊还是线程A这哥们动作比较快,先获取到了锁

(2)然后线程A发现条件不满足,想了想,算了,我先释放锁,睡个觉,等条件满足了,别人再唤醒我,岂不是美滋滋。于是释放了锁,睡觉去了

(3)然后线程B自己可以加锁了,执行了一些业务逻辑,然后去调用notify方法唤醒线程A,嘿兄弟,别睡了,到你了...

(4)线程A醒来之后还是要再去去竞争锁的,也就是醒来之后还要竞争将_count修改为1,竞争_owner指向自己毕竟它还在synchronized代码块内部嘛,只有获取锁之后才能执行synchronized代码块的代码。所以只有它再次获取到锁了之后,才会执行代码块内部的逻辑。

因为waitset集合是monitor对象的一个属性,所以调用之前必须要获取到monitor对象的操作权限,也就是获取到锁,notify要操作waitset也是一样。

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

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

【论文速看】DL最新进展20241002-自动驾驶、自监督学习、扩散模型、多模态与图像分割

目录 【自动驾驶】【自监督学习】【扩散模型】【多模态与图像分割】 【自动驾驶】 [轨迹预测] CASPFormer: Trajectory Prediction from BEV Images with Deformable Attention 论文链接:https://arxiv.org/pdf/2409.17790 代码链接:无 运动预测是自动…

基于深度学习的乳腺癌分类识别与诊断系统

温馨提示:文末有 CSDN 平台官方提供的学长 QQ 名片 :) 1. 项目简介 乳腺癌是全球最常见的癌症之一,早期诊断对于治疗效果至关重要。近年来,深度学习技术在医学图像分析领域取得了显著进展,能够从大量的医学影像数据中自动学习和提…

[动态规划] 二叉树中的最大路径和##树形DP#DFS

标题:[动态规划] 二叉树中的最大路径和##树形DP#DFS 个人主页水墨不写bug (图片来源于网络) 目录 一 、什么是树形DP 二、题目描述(点击题目转跳至题目) NC6 二叉树中的最大路径和 算法思路: 讲解与参考代…

建筑业挂靠行为的防范建议

在建筑行业中,挂靠行为的普遍存在给许多企业带来了法律风险和信誉风险。为了防范这些风险,企业需要采取一系列有效的措施。 一、加强资质管理 企业应当通过合法途径获取和提升自身的资质等级,避免因资质不足而产生挂靠的需求。加强资质管理是…

Python从入门到高手4.2节-掌握循环控制语句

目录 4.2.1 理解循环控制 4.2.2 for循环结构 4.2.3 循环结构的else语句 4.2.4 while循环结构 4.2.5 循环结构可以嵌套 4.2.6 国庆节吃好玩好 4.2.1 理解循环控制 我们先来搞清楚循环的含义。以下内容引自汉语词典: 循环意指往复回旋,指事物周而复始地运动或变…

html+css+js实现Collapse 折叠面板

实现效果&#xff1a; HTML部分 <div class"collapse"><ul><li><div class"header"><h4>一致性 Consistency</h4><span class"iconfont icon-jiantou"></span></div><div class"…

Linux中的进程间通信之共享内存

共享内存 共享内存示意图 共享内存数据结构 struct shmid_ds {struct ipc_perm shm_perm; /* operation perms */int shm_segsz; /* size of segment (bytes) */__kernel_time_t shm_atime; /* last attach time */__kernel_time_t shm_dtime; /* last detach time */__kerne…

【Java】—— 集合框架:List接口常用方法与List接口的实现类

目录 4. Collection子接口1&#xff1a;List 4.1 List接口特点 4.2 List接口方法 4.3 List接口主要实现类&#xff1a;ArrayList 4.4 List的实现类之二&#xff1a;LinkedList 4.5 List的实现类之三&#xff1a;Vector 4.6 练习 4. Collection子接口1&#xff1a;List …

【Docker】docker的存储

介绍 docker存储主要是涉及到3个方面&#xff1a; 第一个是容器启动时需要的镜像 镜像文件都是基于图层存储驱动来实现的&#xff0c;镜像图层都是只读层&#xff0c; 第二个是&#xff1a; 容器读写层&#xff0c; 容器启动后&#xff0c;docker会基于容器镜像的读层&…

【python实操】python小程序之随机抽签以及for循环计算0-x的和

引言 python小程序之随机抽签以及for循环计算0-x的和 文章目录 引言一、随机抽签1.1 题目1.2 代码1.3 代码解释 二、for循环计算0-x的和2.1 题目2.2 代码2.3 代码解释 三、思考3.1 随机抽签3.2 for循环计算0-x的和 一、随机抽签 1.1 题目 使用input输入五个同学的名字随机抽取…

C++(Qt)软件调试---内存调试器Dr.Memory(21)

C(Qt)软件调试—内存调试器Dr. Memory&#xff08;21&#xff09; 文章目录 C(Qt)软件调试---内存调试器Dr. Memory&#xff08;21&#xff09;[toc]1、概述&#x1f41c;2、安装Dr.Memory&#x1fab2;3、命令行使用Dr.Memory&#x1f997;4、Qt Creator集成使用Dr.Memory&…

主流HR软件对比,五大系统功能与成本一览

五款主流HR系统包括ZohoPeople、金蝶人力云、用友人力云、红海eHR和SAPSuccessFactors&#xff0c;各具特色。ZohoPeople功能丰富&#xff0c;金蝶人力云云端部署&#xff0c;用友人力云多模块集成&#xff0c;红海eHR定制化服务&#xff0c;SAPSuccessFactors全球化视野。企业…

vite中sass警告JS API过期

1.问题 在Vite创建项目中引入Sass弹出The legacy JS API is deprecated and will be removed in Dart Sass 2.0.0 - vite中sass警告JS API过期 The legacy JS API is deprecated and will be removed in Dart Sass 2.0.0警告提示表明你当前正在使用的 Dart Sass 版本中&#…

VisionTS:基于时间序列的图形构建高性能时间序列预测模型,利用图像信息进行时间序列预测

构建预训练时间序列模型时面临的主要挑战是什么&#xff1f;获取高质量、多样化的时间序列数据。目前构建基础预测模型主要有两种方法&#xff1a; 迁移学习LLM&#xff1a;通过针对时间序列任务定制的微调或分词策略&#xff0c;重新利用预训练的大型语言模型&#xff08;LLM…

CertiK《Hack3d:2024年第三季度安全报告》(附报告全文链接)

CertiK《Hack3d&#xff1a;2024年第三季度Web3.0安全报告》现已发布&#xff0c;本次报告深入分析了2024年7月至9月的链上安全状况&#xff0c;本季度总损失金额为7.53亿美元&#xff0c;网络钓鱼和私钥泄露是本季度造成资产损失的主要原因。 ​ 关键数据 2024年第三季度&a…

用Python实现运筹学——Day 9: 线性规划的灵敏度分析

一、学习内容 1. 灵敏度分析的定义与作用 灵敏度分析&#xff08;Sensitivity Analysis&#xff09; 是在优化问题中&#xff0c;分析模型参数变化对最优解及目标函数值的影响。它帮助我们了解在线性规划模型中&#xff0c;当某些参数&#xff08;如资源供应量、成本系数等&a…

【C语言】数组(下)

6、二维数组的创建 6.1二维数组的概念 通过数组&#xff08;上&#xff09;介绍&#xff0c;我们学习了一维数组&#xff0c;数组的元素都是内置类型的&#xff0c;如果我们把一维数组作为数组的元素&#xff0c;这时就是二维数组&#xff0c;以此类推&#xff0c;如果把二维…

Mysql 索引底层数据结构和算法

索引数据结构 索引&#xff08;index&#xff09;是帮助MySQL高效获取数据的一种有序数据结构。索引是存储到表空间中&#xff0c;当我们的 sql 中的where条件用到索引的时候&#xff0c;会在存储层就过滤出数据来&#xff0c;如果不走索引&#xff0c;则需要在server层过滤。 …

5分钟学会SPI

SPI 定义&#xff1a;SPI 是一种机制&#xff0c;允许用户在不修改现有代码的情况下扩展和替换特定服务的实现。它定义了一组接口&#xff08;Service Interfaces&#xff09;和一组实现&#xff08;Service Providers&#xff09;&#xff0c;使得应用程序可以动态加载和使用…

Linux:进程控制(一)

目录 一、写时拷贝 1.创建子进程 2.写时拷贝 二、进程终止 1.函数返回值 2.错误码 3.异常退出 4.exit 5._exit 一、写时拷贝 父子进程&#xff0c;代码共享&#xff0c;不作写入操作时&#xff0c;数据也是共享的&#xff0c;当任意一方试图写入&#xff0c;便通过写时拷…