【Java并发】synchronized关键字的底层原理

文章目录

    • 1.synchronized作用
    • 2.synchronized加锁原理
    • 3.monitor锁
    • 4.synchronized锁的优化
      • 4.1.自适应性自旋锁
      • 4.2.偏向锁
      • 4.3.轻量级锁
      • 4.3.重量级锁
    • 5.总结

1.synchronized作用

synchronized是Java提供一种隐式锁无需开发者手动加锁释放锁。保证多线程并发情况下数据的安全性,实现了同一个时刻只有一个线程能访问资源,其他线程只能阻塞等待,简单说就是互斥同步。

2.synchronized加锁原理

代码块加锁:
例如下面一段代码就是加上了对象锁
在这里插入图片描述
这个时候通过反编译查看class字节码信息:
在这里插入图片描述
可以看到,底层是通过monitorentermonitorexit两个关键字实现的加锁释放锁,执行同步代码之前使用monitorenter加锁,执行完同步代码使用monitorexit释放锁,抛出异常的时候也是用monitorexit释放锁。

在方法上加锁
在这里插入图片描述
反编译看一下底层实现
在这里插入图片描述
这次只使用了一个ACC_SYNCHRONIZED关键字,实现了隐式的加锁与释放锁。其实无论是ACC_SYNCHRONIZED关键字,还是monitorenter和monitorexit,底层都是通过获取monitor锁来实现的加锁释放锁

3.monitor锁

Monitor 被翻译为监视器,是由jvm提供,c++语言实现

monitor锁是通过ObjectMonitor来实现的,虚拟机中ObjectMonitor数据结构如下(C++实现的):
在这里插入图片描述
其中:

  1. Owner(持有锁的线程-只能有一个):存储当前获取锁的线程的,只能有一个线程可以获取
  2. EntryList(保存竞争的线程):关联没有抢到锁的线程,处于Blocked状态的线程
  3. WaitSet:关联调用了wait方法的线程,处于Waiting状态的线程

执行逻辑:
在这里插入图片描述
在这里插入图片描述
图上展示了ObjectMonitor的基本工作机制:

  1. 当多个线程同时访问一段同步代码时,首先会进入 _EntryList 队列中等待。
  2. 当某个线程获取到对象的Monitor锁后进入临界区域,并把Monitor中的 _owner变量设置为当前线程,同时Monitor中的计数器 _count 加1。即获得对象锁。
  3. 若持有Monitor的线程调用 wait()方法,将释放当前持有的Monitor锁,_owner变量恢复为null_count减1,同时该线程进入 _WaitSet集合等待被唤醒。
  4. 在_WaitSet 集合中的线程唤醒后会被再次放到_EntryList 队列中,重新竞争获取锁。
  5. 若当前线程执行完毕也将释放Monitor并复位_owner变量的值,以便其他线程进入获取锁。

4.synchronized锁的优化

JDK1.5之前,synchronized是属于重量级锁(Monitor实现的锁属于重量级锁),涉及到了用户态和内核态的切换进程的上下文切换成本较高性能比较低。
在JDK 1.6引入了两种新型锁机制:偏向锁轻量级锁,它们的引入是为了解决在没有多线程竞争基本没有竞争的场景下因使用传统锁机制带来的性能开销问题。

4.1.自适应性自旋锁

自旋锁:在没有拿到锁的时候,当前线程会进入阻塞状态,当持有锁的线程释放了锁,当前线程才可以再去竞争锁。在线程占用锁的时间很短的话。会浪费大量的性能阻塞和唤醒的切换上。

为了避免阻塞和唤醒的切换,在没有获得锁的时候就不进入阻塞,而是不断地循环检测锁是否被释放,这就是自旋。在占用锁的时间短的情况下,自旋锁表现的性能是很高的。

自适应性自旋锁:是对自旋锁的一次升级,自适应性自旋锁的意思是,自旋的次数不是固定的,而是由前一次在同一个锁上的自旋时间锁的拥有者的状态来决定

举例就是此次自旋成功了,很有可能下一次也能成功,于是允许自旋的次数就会更多,反过来说,如果很少有线程能够自旋成功,很有可能下一次也是失败,则自旋次数就更少。这样能最大化利用资源

4.2.偏向锁

不存在多线程竞争,而且总是由同一线程多次获得锁(同一线程可重入锁)
例如如下代码:
同一个线程多次获得锁
加锁m1—>m2—>m3
释放锁m3—>m2—>m1
期间并不存在竞争,不存在阻塞等待,也不存在唤醒。
在这里插入图片描述
在这里插入图片描述
原理:
锁的争夺实际上是Monitor对象的争夺,还有每个对象都有一个对象头,对象头是由Mark Word和Klass pointer 组成的。一旦有线程持有了这个锁对象,标志位修改为1,就进入偏向模式,同时会把这个线程的ID记录在对象的Mark Word中,当同一个线程再次进入时,就不再进行同步操作,这样就省去了大量的锁申请的操作,从而提高了性能。

只有第一次使用 CAS 将线程 ID 设置到对象的 Mark Word 头,之后发现
这个线程 ID自己的表示没有竞争,不用重新 CAS。以后只要不发生竞争,这个对象就归该线程所有

一旦不同的线程来获取锁的时候,那么偏向锁发现Mark Word中线程id不一样了,就会向上升级为轻量级锁(不会直接升级到重量级锁)

4.3.轻量级锁

在很多的情况下,在Java程序运行时,同步块中的代码都是不存在竞争的不同的线程交替的执行同步块中的代码。(交替执行自然不存在线程竞争)
在这里插入图片描述
原理:
执行同步代码块之前,JVM会在线程的栈帧中创建一个锁记录(Lock Record),并将Mark Word拷贝复制到锁记录中。然后尝试通过CAS操作将Mark Word中的锁记录的指针,指向创建的Lock Record。如果成功表示获取锁状态成功,如果失败,则进入自旋获取锁状态。

在这里插入图片描述

自旋获取锁也失败了,则升级为重量级锁,也就是把线程阻塞起来,等待唤醒。

4.3.重量级锁

也就是上述的synchronized锁,就是一个重量级锁

重量级锁就是一个悲观锁了,但是其实不是最坏的锁,因为升级到重量级锁,是因为线程占用锁的时间长(自旋获取失败),锁竞争激烈的场景,在这种情况下,让线程进入阻塞状态,进入阻塞队列,能减少cpu消耗。所以说在不同的场景使用最佳的解决方案才是最好的技术。synchronized在不同的场景会自动选择不同的锁,这样一个升级锁的策略就体现出了这点。

5.总结

Java中的synchronized有偏向锁轻量级锁重量级锁三种形式,分别对应了锁只被一个线程持有不同线程交替持有锁多线程竞争锁三种情况。

对应情况
偏向锁只被一个线程持有
轻量级锁不同线程交替持有锁
重量级锁多线程竞争锁
描述
重量级锁底层使用的Monitor实现,里面涉及到了用户态和内核态的切换、进程的上下文切换,成本较高,性能比较低
轻量级锁线程加锁的时间是错开的(也就是没有竞争),可以使用轻量级锁来优化。轻量级修改了对象头的锁标志,相对重量级锁性能提升很多。每次修改都是CAS操作,保证原子性
偏向锁一段很长的时间内都只被一个线程使用锁,可以使用了偏向锁,在第一次获得锁时,会有一个CAS操作之后该线程再获取锁,只需要判断mark word中是否是自己的线程id即可,而不是开销相对较大的CAS命令

一旦锁发生了竞争,都会升级为重量级锁

参考来自:黑马程序员,公众号:java技术爱好者、一灯架构

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

CycleGAN论文解读及代码实现

paper: https://arxiv.org/pdf/1703.10593.pdf github: https://github.com/aitorzip/PyTorch-CycleGAN 1 cycleGAN 小结 网络: 生成器2个:G_A,G_B 判别器两个: D_A,D_B损失函数8个 6个生成器损失函数 2个判别器损失…

Cesium相机理解

关于cesium相机,包括里面内部原理网上有很多人讲的都很清楚了,我感觉这两个人写的都挺好得: 相机 Camera | Cesium 入门教程 (syzdev.cn) Cesium中的相机—setView&lookAtTransform_cesium setview_云上飞47636962的博客-CSDN博客上面这…

记录线上一次mysql只能查询,不能插入或更新的bug

错误复现 突然有一天产品通知xx服务不可用,想着最近也没有服务更新,就先排查一下服务日志 使用postman测试的时候请求明显超时,查看日志显示是一个锁的问题 使用工具连接到mysql,查看information_schema.INNODB_TRX,发现有一个事…

docker删除容器时报错:Error response from daemon: reference does not exist

前言 之前使用的docker版本太低了,升级高版本docker之后的错误。 低版本docker(1.30.1)中的镜像有:golang、mysql,将docker升级为24.0.5并新拉取mysql最新版本之后,执行docker images命令,发现…

构建Docker容器监控系统(2)(Cadvisor +Prometheus+Grafana)

Cadvisor产品简介 Cadvisor是Google开源的一款用于展示和分析容器运行状态的可视化工具。通过在主机上运行Cadvisor用户可以轻松的获取到当前主机上容器的运行统计信息,并以图表的形式向用户展示。 接着上一篇来继续 部署Cadvisor 被监控主机上部署Cadvisor容器…

比较研发项目管理系统:哪个更适合您的需求?

项目管理系统对于保持项目进度、提高效率和确保质量至关重要。然而,市场上众多的研发项目管理系统让许多团队陷入选择困难。本文将对几个主流的研发项目管理系统进行深入分析,以帮助您找到最适合您团队的解决方案。 “哪个研发项目管理系统好用好&#x…

在时间和频率域中准确地测量太阳黑子活动及使用信号处理工具箱(TM)生成广泛的波形,如正弦波、方波等研究(Matlab代码实现)

💥💥💞💞欢迎来到本博客❤️❤️💥💥 🏆博主优势:🌞🌞🌞博客内容尽量做到思维缜密,逻辑清晰,为了方便读者。 ⛳️座右铭&a…

vue3获得url上的参数值

1、引入 import { useRoute } from vue-router2、获得const route useRoute() console.log(route.query.number)

程序员必备技能-九大分布式ID生成策略

九大分布式ID生成策略 1.UUID UUID (Universally Unique Identifier),通用唯一识别码。UUID是基于当前时间、计数器(counter)和硬件标识(通常为无线网卡的MAC地址)等数据计算生成的。 UUID由以下几部分的组合&#x…

《Python入门到精通》os模块详解,Python os标准库

「作者主页」:士别三日wyx 「作者简介」:CSDN top100、阿里云博客专家、华为云享专家、网络安全领域优质创作者 「推荐专栏」:小白零基础《Python入门到精通》 os模块详解 1、文件目录操作os.stat() 获取文件状态os.utime() 修改文件时间os.r…

vuejs 设计与实现 - 简单diff算法

DOM 复用与key的作用: DOM 复用什么时候可复用? key 属性就像虚拟节点的“身份证”号,只要两个虚拟节点的 type属性值和 key 属性值都相同,那么我们就认为它们是相同的,即可以进行 DOM 的复用。即 我们通过【移动】来…

无需公网-用zerotier异地组网

无需公网-用zerotier异地组网 在前面的文章中我们讲到利用frp进行内网穿透,但是他的局限在于你需要一台公网服务器。并且对公网服务器的带宽有一定的要求。因此这里我们推荐一款异地组网工具搭建属于自己的虚拟网络,经过授权连接成功之后彼此都在同一网…

Oracle单实例升级补丁

目录 1.当前DB环境2.下载补丁包和opatch的升级包3.检查OPatch的版本4.检查补丁是否冲突5.关闭数据库实例,关闭监听6.应用patch7.加载变化的SQL到数据库8.ORACLE升级补丁查询 oracle19.3升级补丁到19.18 1.当前DB环境 [oraclelocalhost ~]$ cat /etc/redhat-releas…

\vendor\github.com\godror\orahlp.go:531:19: undefined: VersionInfo

…\goAdmin\vendor\github.com\godror\orahlp.go:531:19: undefined: VersionInfo 解决办法 降了go版本(go1.18),之前是go1.19 gorm版本不能用最新的,降至(gorm.io/gorm v1.21.16)就可以 修改交插编译参数 go env -w CGO_ENABLED1…

# ⛳ Docker 安装、配置和详细使用教程-Win10专业版

目录 ⛳ Docker 安装、配置和详细使用教程-Win10专业版🚜 一、win10 系统配置🎨 二、Docker下载和安装🏭 三、Docker配置🎉 四、Docker入门使用 ⛳ Docker 安装、配置和详细使用教程-Win10专业版 🚜 一、win10 系统配…

区块链实验室(15) - 编译FISCO BCOS的过程监测

首次编译开源项目,一般需要下载很多依赖包,尤其是从github、sourceforge等下载依赖包时,速度很慢,编译进度似乎没有一点反应,似乎陷入死循环,似乎陷入一个没有结果的等待。本文提供一种监测方法&#xff0c…

redis的事务和watch机制

这里写目录标题 第一章、redis事务和watch机制1.1)redis事务,事务的三大命令语法:开启事务 multi语法:执行事务 exec语法:取消事务 discard 1.2)redis事务的错误和回滚的情况1.3)watch机制语法&…

【Linux】为.sh脚本制作桌面快捷方式(.desktop,可双击执行),且替换显示图标(图文详情)

目录 0.背景环境 1、原理 2、详细步骤 1)创建.desktop快捷方式 2) 给test.desktop快捷方式增加可执行权限 3)编辑test.desktop内容和参数 4)修改快捷方式属性为双击可执行 5)将桌面快捷方式发送到桌面 0.背景环…

2023全新UI好看的社区源码下载/反编译版

2023全新UI好看的社区源码下载/反编译版 这次分享一个RuleAPP二开美化版(尊重每个作者版权),无加密可反编译版本放压缩包了,自己弄吧!!! RuleAPP本身就是一款免费开源强大的社区,基…

交替方向乘子

目录 一,交替方向乘子ADMM 1,带线性约束的分离优化模型 2,常见优化模型转带线性约束的分离优化模型 3,带线性约束的分离优化模型求解 4,交替方向乘子ADMM 本文部分内容来自教材 一,交替方向乘子ADMM …