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

news2024/11/15 20:09:50
一、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.coloradmin.cn/o/2185228.html

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

相关文章

3 个简单的微分段项目

与许多大型网络安全项目一样,微分段似乎很复杂、耗时且成本高昂。 它涉及管理有关设备间服务连接的复杂细节。 一台 Web 服务器应连接到特定数据库,但不连接到其他数据库,或者负载平衡器应连接到某些 Web 服务器,同时限制与其他…

图解大模型计算加速系列:vLLM源码解析1,整体架构

整个vLLM代码读下来,给我最深的感觉就是:代码呈现上非常干净历练,但是逻辑比较复杂,环环嵌套,毕竟它是一个耦合了工程调度和模型架构改进的巨大工程。 所以在源码解读的第一篇,我想先写一下对整个代码架构…

Golang | Leetcode Golang题解之第449题序列化和反序列化二叉搜索树

题目: 题解: type Codec struct{}func Constructor() (_ Codec) { return }func (Codec) serialize(root *TreeNode) string {arr : []string{}var postOrder func(*TreeNode)postOrder func(node *TreeNode) {if node nil {return}postOrder(node.Le…

java基础 day1

学习视频链接 人机交互的小故事 微软和乔布斯借鉴了施乐实现了如今的图形化界面 图形化界面对于用户来说,操作更加容易上手,但是也存在一些问题。使用图形化界面需要加载许多图片,所以消耗内存;此外运行的速度没有命令行快 Wi…

针对考研的C语言学习(2019链表大题)

题目解析: 【考】双指针算法,逆置法,归并法。 解析:因为题目要求空间复杂度为O(1),即不能再开辟一条链表,因此我们只能用变量来整体挪动原链表。 第一步先找出中间节点 typedef NODE* Node; Node find_m…

latex有哪些颜色中文叫什么,Python绘制出来

latex有哪些颜色中文叫什么,Python绘制出来 为了展示xcolor包预定义的颜色及其对应的中文名称,并使用Python打印出来,我们可以先列出常见的预定义颜色名称,然后将它们翻译成中文,并最后用Python打印出来。 步骤 列出…

家庭记账本的设计与实现+ssm(lw+演示+源码+运行)

摘 要 随着我国经济迅速发展,人们对手机的需求越来越大,各种手机软件也都在被广泛应用,但是对于手机进行数据信息管理,对于手机的各种软件也是备受用户的喜爱,家庭记账本小程序被用户普遍使用,为方便用户能…

MySQL高阶2066-账户余额

目录 题目 准备数据 分析数据 总结 题目 请写出能够返回用户每次交易完成后的账户余额. 我们约定所有用户在进行交易前的账户余额都为0, 并且保证所有交易行为后的余额不为负数。 返回的结果请依次按照 账户(account_id), 日期( day ) 进行升序排序…

leetcode_238:除自身以外数组的乘积

给你一个整数数组 nums,返回 数组 answer ,其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 。 题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。 请 不要使用除法,且在 O(n) 时间复杂…

Conditional Generative Adversarial Nets

条件生成对抗网络 1.生成对抗网络 生成对网络由两个“对抗性”模型组成:一个生成模型 G,用于捕获数据分布,另一个判别模型 D,用于估计样本来自训练数据而不是 G 的概率。G 和 D 都可以是非线性映射函数。 为了学习数据 x 上的生…

设计模式-生成器模式/建造者模式Builder

构建起模式:将一个复杂类的表示与其构造分离,使得相同的构建过程能够得出不同的表示。(建造者其实和工厂模式差不多) 详细的UML类图 图文说明:距离相同的构建过程 得出不同的展示。此时就用两个类(文本生成…

探索未来:hbmqtt,Python中的AI驱动MQTT

文章目录 **探索未来:hbmqtt,Python中的AI驱动MQTT**1. 背景介绍2. hbmqtt是什么?3. 安装hbmqtt4. 简单的库函数使用方法4.1 连接到MQTT服务器4.2 发布消息4.3 订阅主题4.4 接收消息4.5 断开连接 5. 应用场景示例5.1 智能家居控制5.2 环境监测…

WebGIS之Cesium三维软件开发

目录 第 1 章 三维 WebGIS 概述 1.1 Google Earth 1 1.2 SkylineGlobe 2 1.3 LocaSpace Viewe 2 1.4 Cesium 3 1.5 Cesium API 概要 4 第 2 章 Cesium 快速入门 2.1 Cesium 环境搭建 7 2.1.1 安装 Node.js 环境 7 2.1.2 配置 Cesium 依赖 8 2.2 搭建第一个 Cesi…

【2006.07】UMLS工具——MetaMap原理深度解析

文献:《MetaMap: Mapping Text to the UMLS Metathesaurus》2006 年 7 月 14 日 https://lhncbc.nlm.nih.gov/ii/information/Papers/metamap06.pdf MetaMap:将文本映射到 UMLS 元数据库 总结 解决的问题 自动概念映射问题:解决如何将文本…

Vue3丨进一步了解这 20 个响应式 API,写码如有神

前面说的话 在 Vue2 中,个人觉得对于数据的操作比较 “黑盒” 。而 Vue3 把响应式系统更显式地暴露出来,使得我们对数据的操作有了更多的灵活性。所以,对于 Vue3 的几个响应式的 API ,我们需要更加的理解掌握,才能在实…

【MySQL】子查询、合并查询、表的连接

目录 一、子查询 1、单行子查询 显示SMITH同一部门的员工信息 2、多行子查询 in关键字 查询和10号部门的工作岗位相同的雇员的名字、岗位、工资、部门号,但是筛选出的雇员的部门不能有10号部门 all关键字 查询工资比30号部门中所有雇员工资高的雇员的姓名、…

TS(type,属性修饰符,抽象类,interface)一次性全部总结

目录 1.type 1.基本用法 2.联合类型 3.交叉类型 2.属性修饰符 1.public 属性修饰符 属性的简写形式 2.proteced 属性修饰符 3.private 属性修饰符 4.readonly 属性修饰符 3.抽象类 4.interface 1.定义类结构 2.定义对象结构 3.定义函数结构 4.接口之间的继…

postgresql|数据库|postgis编译完成后的插件迁移应该如何做(postgis插件最终章)

一、 本文的写作理由 postgis插件一般是编译安装,编译安装的原因是可以选择自己喜欢的版本,但编译的难度也是比较高的,因为有各种依赖,依赖之间还有依赖,非常容易形成依赖循环,因此,失败率是比…

【Python】CSVKit:强大的命令行CSV工具套件

CSVKit 是一个基于命令行的工具集,用于简化 CSV 文件的处理和管理。它提供了从数据转换、筛选、格式化到分析的全方位支持,特别适合需要处理复杂表格数据的用户。相比传统的 Excel 操作,CSVKit 更高效且功能更强大,非常适合数据分…

VSOMEIP代码阅读整理(1) - 网卡状态监听

一. 概述 在routing进程所使用的配置文件中,存在如下配置项目:{"unicast" : "192.168.56.101",..."service-discovery" :{"enable" : "true","multicast" : "224.244.224.245",…