JUC并发编程 06——Synchronized与锁升级

一.Java对象内存布局和对象头

在HotSpot虚拟机里,对象在堆内存中的存储布局可以划分为三个部分:对象头(Header) 、实例数据 (Instance Data) 和 对文填充 (Padding)。

  • 对象内部结构分为:对象头、实例数据、对齐填充(保证8个字节的倍数)。
  • 对象头分为对象标记(markOop)和类元信息(klassOop),类元信息存储的是指向该对象类元数据(klass)的首地址。

对象头

在64位系统中,Mark Word占了8个字节,类型指针占了4个字节(采用了压缩指针,压缩前是8个)。

对象标记Mark Word

类元信息(又叫类型指针)

指向它的类元数据(方法区的Klass对象)的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。

实例数据

存放类的属性(Field)数据信息,包括父类的属性信息。

对齐填充

虚拟机要求对象起始地址必须是8字节的整数倍。填充数据不是必须存在的,仅仅是为了字节对齐
这部分内存按8字节补充对齐。

二.Synchronized与锁升级

2.1为什么 JDK1.6 以前synchronized是重量级锁

什么是上下文操作

线程多,不断阻塞、唤醒,一次阻塞+唤醒操作需要两次上下文切换:

  1. 就是第一次由用户态切换到内核态,由内核将线程阻塞掉,将线程数据原本在寄存器或者cache运送到内存或队列中
  2. 第二次由用户态切换到内核态,由内核将线程唤醒,将线程数据从内存或者队列送到寄存器或cache中。如果频繁进行上下文切换,CPU就大多数消耗在内核数据共享中,影响了真正工作线程功能

上下文切换过高会导致CPU像个搬运工,频繁在寄存器和运行队列之间奔波 ,更多的时间花在了线程切换,而不是真正工作的线程上。直接的消耗包括CPU寄存器需要保存和加载,系统调度器的代码需要执行。间接消耗在于多核cache之间的共享数据。

因为监视器锁(monitor)依赖底层操作系统的原语 pthread_mutex_lock 实现,当多个线程同时调用这个函数的时候,会让每一个线程都切换到内核态,由内核协调哪个线程获取到锁,哪些线程无法获取到锁。获取锁成功的线程会被唤醒,获取锁失败的线程会被内核进行阻塞。所以它是一个重量级操作操作系统实现线程之间的切换需要从用户态到内核态的转换,这种状态切换需要耗费处理器时间。

总之一句话,重量级锁是需要依靠操作系统来实现互斥锁的,这导致大量上下文切换,消耗大量CPU,影响性能。

Java SE 1.6开始,为了减少获得锁和释放锁所带来的性能消耗,引入了轻量级锁偏向锁。在Java SE 1.6中,锁一共有4种状态,级别从低到高依次是:无锁状态、偏向锁状态、轻量级锁状态和重量级锁状态,这几个状态会随着竞争情况逐渐升级。锁可以升级但不能降级,意味着偏向锁升级成轻量级锁后不能降级为偏向锁。这种锁升级却不能降级的策略,目的是为了提高获得锁和释放锁的效率。

2.2偏向锁

Hotspot 的作者经过研究发现,大多数情况下:锁不仅不存在多线程竞争,还存在锁由同一线程多次获得的情况,为了让线程获得锁的代价更低而引入了偏向锁,这个线程就是锁的偏向线程,它的出现是为了解决只有在一个线程执行同步时提高性能。

  1. 首先获取锁对象的 Markword,判断是否处于可偏向状态。(biased_lock=1、且 ThreadId 为空)
  2. 如果是可偏向状态,则通过 CAS 操作,把当前线程的 ID 写入到 MarkWord
    1. 如果 cas 成功,那么 markword 就会变成这样。 表示已经获得了锁对象的偏向锁,接着执行同步代码块
    2. 如果 cas 失败,说明有其他线程已经获得了偏向锁,这种情况说明当前锁存在竞争,需要撤销已获得偏向锁的线程,并且把它持有的锁升级为轻量级锁(这个操作需要等到全局安全点,也就是没有线程在执行字节码)才能执行
  3. 如果是已偏向状态,需要检查 markword 中存储的 ThreadID 是否等于当前线程的 ThreadID
    1. 如果相等,不需要再次获得锁,可直接执行同步代码块
    2. 如果不相等,说明当前锁偏向于其他线程,需要撤销偏向锁并升级到轻量级锁

偏向锁撤销

偏向锁使用一种等到竞争出现才释放锁的机制,只有当其他线程竞争锁时,持有偏向锁的原来线程才会被撤销。撤销需要等待全局安全点(该时间点上没有字节码正在执行),同时检查持有偏向锁的线程是否还在执行: 

  1. 第一个线程正在执行synchronized方法(处于同步块),它还没有执行完,其它线程来抢夺,该偏向锁会被取消掉并出现锁升级此时轻量级锁由原持有偏向锁的线程持有,继续执行其同步代码,而正在竞争的线程会进入自旋等待获得该轻量级锁。
  2. 第一个线程执行完成synchronized方法(退出同步块),则将对象头设置成无锁状态并撤销偏向锁,重新偏向 。

关闭偏向锁

偏向锁在 Java 6 和 Java 7 里是默认启用的,但是它在应用程序启动几秒钟之后才激活,如有必要可以使用JVM参数来关闭延迟:-XX:BiasedLockingStartupDelay=0

如果你确定应用程序里所有的锁通常情况下处于竞争状态,那么偏向锁就是多余的,此时使用偏向锁是得不到提升的,反而可能降低系统性能,可以通过JVM参数关闭偏向锁:-XX:-UseBiasedLocking

从 JDK 15 开始,偏向锁被官方标记为废弃状态

2.3轻量级锁

轻量级锁加锁

加锁的过程很奇妙:线程在执行同步代码块之前,JVM会先在当前线程的栈帧中创建用于存储锁记录的空间,并将对象头中的Mark Work 复制到锁记录中。接下来线程会尝试使用CAS操作将对象头中的Mark Word替换为指向锁记录的指针。如果成功则获取锁,失败则表示被占用,自旋重试。

轻量级锁解锁

轻量级锁解锁时,会使用原子性的CAS操作将栈帧中的 Mark Word替换回到对象头,如果成功则表示没有竞争。失败则表示存在竞争,锁膨胀为重量级锁。

经过上面的学习,我们知道了JVM底层几种锁的实现,下面总结了几种锁的优缺点

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

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

相关文章

单机多卡训练-DDP

DDP原理: 为什么快? DDP通过Ring-Reduce(梯度合并)的数据交换方法提高了通讯效率,并通过启动多个进程的方式减轻Python GIL的限制,从而提高训练速度。 神经网络中的并行有以下三种形式: Data…

软件设计师——计算机组成原理(三)

📑前言 本文主要是【计算机组成原理】——软件设计师——计算机组成原理的文章,如果有什么需要改进的地方还请大佬指出⛺️ 🎬作者简介:大家好,我是听风与他🥇 ☁️博客首页:CSDN主页听风与他 …

runCatching异常捕获onSuccess/onFailure返回函数,Kotlin

runCatching异常捕获onSuccess/onFailure返回函数,Kotlin fun test(a: Int, b: Int) {runCatching {a / b}.onSuccess {println("onSuccess: $it")return ok(it)}.onFailure {println("onFailure: $it")return fail(it)} }fun ok(o: Any) {prin…

echarts地图的常见用法:基本使用、区域颜色分级、水波动画、区域轮播、给地图添加背景图片和图标、3d地图、飞线图

前言 最近几天用echarts做中国地图,就把以前写的demo:在vue中实现中国地图 拿来用,结果到项目里直接报错了,后来发现是因为版本的问题,没办法只能从头进行踩坑了。以下内容基于vue3 和 echarts 5.32 基本使用 获取地…

Linux环境下HTTP请求的代码详解与实例

嘿,Linux狂热者们,是时候让我们在这个充满激情的平台上探索一下HTTP协议的奥秘了。我知道,对于这个我们每天都在使用,但却可能没深入了解过的HTTP,你们一定充满了好奇和期待。那么,让我们一起踏上这趟探索之…

fripside - promise lrc

[ti:promise] [ed:2] [rt:20] [ml:0|0] [00:05.172]words:Satoshi Yaginuma, Shinichiro Yamashita [00:09.664]music&arrangement:Satoshi Yaginuma, Shigetoshi Yamada [00:14.565]PCゲーム「ENGAGE LINKS」 (Alcot) エンディングテーマ [00:20.000] [00:46.442]朝の陽射…

教师如何维护学生的自尊心

作为教师,我们不仅要传授知识,更要关心学生的身心健康,特别是他们的自尊心。自尊心是个人自我价值的重要体现,对学生的学习、生活和未来的发展都有深远的影响。因此,维护学生的自尊心是教师的重要责任。 教师要尊重每…

波奇学Linux:进程终止

写时拷贝底层原理图 子进程谁先运行,由调度器决定 进程退出场景 代码运行完毕,结果正确:有返回值,返回0 代码运行完毕,结果不正确:有返回值,返回非0 代码异常终止。没有返回值 return 0的…

Cuttlefish安装和运行

1.验证KVM可用性(X86机器) grep -c -w "vmx\|svm" /proc/cpuinfo 如果返回非0值,则说明kvm已经配置好。 2.安装Cuttlefish模拟器 sudo apt install git devscripts config-package-dev debhelper-compat golang curlgit clone https://github.com/goo…

十七、如何将MapReduce程序提交到YARN运行

1、启动某个节点的某一个用户 hadoopnode1:~$ jps 13025 Jps hadoopnode1:~$ yarn --daemon start resourcemanager hadoopnode1:~$ jps 13170 ResourceManager 13253 Jps hadoopnode1:~$ yarn --daemon start nodemanager hadoopnode1:~$ jps 13170 ResourceManager 15062 Jp…

【小沐学Python】Python实现通信协议(grpc)

文章目录 1、简介2、安装3、定义接口4、编译生成5、代码编写5.1 服务端5.2 客户端5.3 运行测试 结语 1、简介 https://grpc.io/docs/what-is-grpc/introduction/ https://github.com/grpc/grpc gRPC 是一种现代、开源、高性能的远程过程调用 (RPC) 可以…

Python生成器(Generator)的应用场景和使用(继续更新...)

学习网页: Welcome to Python.orghttps://www.python.org/https://www.python.org/ Python生成器(Generator) 生成器在Python中有很多应用场景,以下是一些常见的应用场景: 处理大数据量和耗时操作的场景&#xff…

Mybatis在新增某个数据后,如何获取新增数据的id

在某些业务中&#xff0c;我们在新增一条数据之后&#xff0c;需要拿到这条数据的id来对这条数据进行后续的一个操作&#xff0c;如何拿取id呢&#xff1f; 使用的是<insert> 中的useGeneratedKeys 和 keyProperty 两个属性 1.在Mybatis Mapper文件中添加属性 “useGene…

【STM32】STM32学习笔记-对射式红外传感器计次 旋转编码器计次(12)

00. 目录 文章目录 00. 目录01. NVIC相关函数1.1 NVIC_PriorityGroupConfig函数1.2 NVIC_PriorityGroup类型1.3 NVIC_Init函数1.4 NVIC_InitTypeDef类型 02. 外部中断相关API2.1 GPIO_EXTILineConfig2.2 EXTI_Init2.3 EXTI_GetITStatus2.4 EXTI_ClearITPendingBit2.5 中断回调函…

Zabbix 优缺点、监控模式、监控项、触发器、动作、自动发现及报警配置等整理:

Zabbix 优缺点&#xff1a; 优点&#xff1a; 开源&#xff0c;无软件使用成本。 使用C语言编写&#xff0c;性能较好&#xff0c;与底层硬件贴合。 丰富的可视化选项。 内置多种监控模板&#xff0c;支持设备种类广泛。 配置简单&#xff0c;服务器端对设备要求不高&…

大数据技术之 Kettle(PDI)

Kettle 第一章 Kettle概述1.1、ETL简介1.2、Kettle简介1.3、作业 和 转换 概念1.4、核心组件1.5、下载安装 第二章 控件使用2.1、初体验&#xff1a;csv 转换 excel 示例2.2、转换2.2.1、输入控件2.2.1.1、表输入 2.2.2、输出控件2.2.2.1、表输出2.2.2.2、更新&插入/更新2.…

【数据结构—队列的实现】

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言 一、队列 1.1队列的概念及结构 二、队列的实现 2.1头文件的实现—Queue.h 2.2源文件的实现—Queue.c 2.3源文件的测试—test.c 三、测试队列实际数据的展示 3.…

获取文件路径里的文件名(不包含扩展名)

“./abc/abc/llf.jpg” 写一个代码&#xff0c;让我获得“llf”这段字符串 import osfile_path "./abc/abc/llf.jpg" file_name os.path.splitext(os.path.basename(file_path))[0] print(file_name)在这个代码中&#xff0c;我们使用了os.path模块来处理文件路径…

LeetCode 每日一题 Day 14(Hard)Day 15|

2276. 统计区间中的整数数目 给你区间的 空 集&#xff0c;请你设计并实现满足要求的数据结构&#xff1a; 新增&#xff1a;添加一个区间到这个区间集合中。 统计&#xff1a;计算出现在 至少一个 区间中的整数个数。 实现 CountIntervals 类&#xff1a; CountIntervals()…

Python语言学习笔记之八(文件IO)

本课程对于有其它语言基础的开发人员可以参考和学习&#xff0c;同时也是记录下来&#xff0c;为个人学习使用&#xff0c;文档中有此不当之处&#xff0c;请谅解。 1、什么是文件I/O 在Python中&#xff0c;文件IO&#xff08;输人/输出)是指与文件进行交互的过程。这包括读…