synchronized 锁优化原理

目录

一、轻量级锁

二、锁膨胀

三、自旋优化

四、偏向锁

五、锁消除


一、轻量级锁

1. 会创建一个锁记录 Lock Record(保存在线程栈中),尝试 CAS 修改 Mark Word 中的对象头,是一种乐观锁的思想,而不是将 Java 对象与操作系统的 Monitor 对象关联起来(重量级锁)

2. CAS 修改

  • mark word 最后两位是 01 表示无锁,最后两位是 00 表示有锁,加锁失败
  • 也有可能是锁的重入(判断对象头的锁记录地址是不是自己),如果是自己执行锁重入,那么再添加一条锁记录 Lock Record 作为锁重入的记录(重入时,锁记录为 null;非重入锁,锁记录是对象头 Mark Word 的值),解锁的时候就是解一次锁去掉一条锁记录,要等栈帧中的所有锁记录被去除才是真的释放了锁

二、锁膨胀

如果在尝试 CAS 替换锁对象的对象头 Mark Word 和锁记录时,替换失败,有种情况就是其他线程对该对象加上了轻量级锁(有竞争),此时就会发生锁膨胀,将轻量级锁升级为重量级锁

  • 加轻量级锁失败,申请 Monitor 锁,让 Object 指向 Monitor,建立 Java 对象和 Monitor 的关联,将 mark word 最后两位变为 10,表示重量级锁
  • 加锁(轻量级锁)失败的线程,加入 Monitor 的阻塞队列
  • 解锁:Thread-0(获取轻量级锁成功的)通过 CAS 将 Mark Word 的值恢复给对象头,由于对象头已经变成了 Monitor 的地址,所以恢复失败,接下来就会走重量级锁的解锁流程,先到 Monitor 中清空 Owner,到阻塞队列唤醒 Thread-1,恢复 Mark Word

三、自旋优化

重量级锁竞争时,线程可以通过自旋来优化,原来需要加入阻塞队列(上下文切换),开销较大,自旋可以重试访问重新判断锁是否可用,自旋成功(在自旋过程中,持有锁的线程退出了同步代码块,释放锁)避免阻塞

  • 自旋会一直消耗 CPU,适用于多核的情况(单核在执行持有锁的线程的任务,自旋线程会被阻塞,没有意义)
  • 问题:可以间隔一段时间再重试吗❓
  • 自旋失败:重试一定次数还是失败就加入阻塞队列
  • Java 6 之后 的自旋锁是自适应的,JVM 底层会根据最近自旋是否成功来判断自旋的次数,最近成功了说明成功概率比较大,就多自旋几次;最近失败了就少重试几次或者不重试;Java 7 之后不能控制是否开启自旋锁,都是由底层来控制是否开启的

四、偏向锁 ‼️

轻量级锁在没有发生竞争时,同一个线程需要重入锁,也需要进行多次 CAS 操作,影响性能

Java 6 中引入的偏向锁来做进一步优化:第一次进行 CAS 操作时,将线程 ID 设置到对象头 Mark Word 中,之后需要重入锁,只需要判断对象头的 ID 是不是自己,不需要进行 CAS 操作

之后只要不发生竞争,这个对象就归该线程所有(问题:如何判断是否发生锁竞争❓锁竞争会将轻量级锁升级为重量级锁,对象头的 Mark Word 会指向 Monitor,释放锁的时候 CAS 失败)

1. 创建对象:如果开启了偏向锁(默认开启),mark word 最后三位是 101,此时 thread、epoch、age 都是 0,但是偏向锁默认是有延迟的,在程序刚启动时不会生效,如果想避免延迟立即生效可以加 JVM 参数;没有开启偏向锁,mark word 最后三位是 001,此时 hashcode、age 都是 0,在第一次用到 hashcode 时才会赋值

问题:创建对象时 101 的意思是已经加了偏向锁还是表示可以加偏向锁❓

2. 加锁:给对象头设置线程 id

3. 释放锁:线程 id 还是保持上一个线程,除非发生锁竞争,这就是偏向锁名字的来由,偏向同一个线程,当该线程再次请求时就不需要再进行复杂的加锁操作

4. 适用场景:单线程多次使用,不适合有多个线程来竞争这把锁的场景,如果知道可能会有冲突,可以通过加 JVM 参数禁用偏向锁,让对象刚创建时是 Normal 正常状态,加锁时后两位是 00(轻量级锁)

5. 加锁顺序:偏向锁 -> 轻量级锁 -> 重量级锁

6. 撤销

  • ⚠️ 注意:调用了 hashcode() 之后就会禁用 / 撤销偏向锁,因为调用 hashcode() 会给 mark word 赋值哈希码,mark word 的大小不够存放线程 id 了,本来可偏向的状态就会被撤销,由 101 变成 001;但是轻量级锁和重量级锁不会有这个问题,因为轻量级锁调用 hashcode() 之后会将哈希码存到锁记录里,重量级锁会存到 monitor 里,解锁时还会还原
  • 释放锁之后其他线程要使用锁:撤销偏向状态(101 -> 001,由可偏向改为不可偏向),将偏向锁升级为轻量级锁(注意不是锁竞争,如果冲突的话是升级为重量级锁了)
  • 调用 wait/notify:也会撤销偏向锁升级为重量级锁,因为 wait/notify 只有重量级锁有

撤销偏向状态比较影响性能:虽然对象被多个线程访问,但并没有发生竞争(t1 释放时候 t2 才访问的),撤销偏向状态升级锁没啥必要,可以将原本偏向 t1 的置为偏向 t2 的(修改 mark word 的线程 id)⬇️

7. 批量重偏向:被多个线程访问但没有发生竞争,修改偏向的线程 id

  • 开启批量重偏向的阈值:当撤销偏向锁的次数达到 20 次之后,JVM 会觉得可能偏向错了,就在加锁时重新偏向加锁线程(第二十次就已经改了)
  • 批量:修改偏向状态是将这批对象的线程 id 都修改成最后一个请求他们锁的线程

8. 批量撤销:撤销偏向达到 40 次(本来就不该偏向),JVM 会对该类的所有对象批量撤销偏向锁,直接进入轻量级锁的状态,新建对象也是 001 不可偏向的

五、锁消除

JVM 的高级优化技术,允许编译器确定锁对象不会引起线程安全问题时,减少不必要的加锁操作,提升程序性能,是 JVM 自动进行的优化,对开发者是透明的

1. JIT 即时编译器会对热点代码(重复调用的)进行优化,如果 synchronized 的对象不会逃离方法的作用范围(局部变量),就可以不加锁,直接消除锁

2. 工作原理

3. 示例场景

  • 如果一个方法内部创建了一个局部变量,对该局部变量进行加锁操作,但是该变量仅存在当前方法栈帧中,不会被其他线程并发访问,所以没有线程安全问题,JVM 就会进行锁消除的优化

4. 优点

  • 性能提升:减少上下文切换和同步开销
  • 简化编程:开发者不需要自己去判断哪些锁是可以避免的,由编译器来自动执行

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

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

相关文章

如何选择适合的接口自动化测试工具!

引言: 在现代软件开发中,接口自动化测试已经成为保证软件质量的重要环节。通过自动化测试工具,可以有效地提高测试效率、减少人力成本,并且能够更好地发现和解决潜在的问题。然而,面对众多的接口自动化测试工具&#…

React+TS前台项目实战(十九)-- 全局常用组件封装:带加载状态和清除等功能的Input组件实现

文章目录 前言Input组件1. 功能分析2. 代码详细注释3. 使用方式4. 效果展示 总结 前言 今天我们来封装一个input输入框组件,并提供一些常用的功能,你可以选择不同的 尺寸、添加前缀、显示加载状态、触发回调函数、自定义样式 等等。这些功能在这个项目中…

数据结构-分析期末选择题考点(排序)

何似清歌倚桃李 一炉沈水醉红灯 契子 ✨ 上一期给大家提供了大概会考的题型给老铁们复习的大致思路 这一期还会是一样,我将整理一下排序的题型以及解题方法给你们 由于时间还很多,我就慢慢总结吧,一天一章的样子,明天总结串、后天…

开发技术-Java集合(List)删除元素的几种方式

文章目录 1. 错误的删除2. 正确的方法2.1 倒叙删除2.2 迭代器删除2.3 removeAll() 删除2.4 removeIf() 最简单的删除 3. 总结 1. 错误的删除 在写代码时,想将其中的一个元素删除,就遍历了 list ,使用了 remove(),发现效果并不是想…

fiddler 返回Raw乱码

有时会发现自己发送的请求后,返回结果Raw里面是乱码,可以勾选Decode并重新发送请求就解决了 这个时候将Decode勾选一下 此时就好了

车辆数据的提取、定位和融合(其二.一 共十二篇)

第一篇: System Introduction 第二篇:State of the Art 第三篇:localization 第四篇:Submapping and temporal weighting 第五篇:Mapping of Point-shaped landmark data 第六篇:Clustering of landma…

基于MDEV的PCI设备虚拟化DEMO实现

利用周末时间做了一个MDEV虚拟化PCI设备的小试验&#xff0c;简单记录一下&#xff1a; DEMO架构&#xff0c;此图参考了内核文档&#xff1a;Documentation/driver-api/vfio-mediated-device.rst host kernel watchdog pci driver: #include <linux/init.h> #include …

yolov8obb角度预测原理解析

预测头 ultralytics/nn/modules/head.py class OBB(Detect):"""YOLOv8 OBB detection head for detection with rotation models."""def __init__(self, nc80, ne1, ch()):"""Initialize OBB with number of classes nc and la…

【Dison夏令营 Day 02】使用 Python 玩井字游戏

在本文中&#xff0c;我们将介绍使用 Python 语言从零开始创建井字游戏的步骤。 在本文中&#xff0c;我们将介绍使用 Python 语言从零开始创建井字游戏的步骤。 游戏简介 井字游戏是一种双人游戏&#xff0c;在 33 正方形网格上进行。每位玩家轮流占据一个单元格&#xff0c…

CMake(1)基础使用

CMake之(1)基础使用 Author: Once Day Date: 2024年6月29日 一位热衷于Linux学习和开发的菜鸟&#xff0c;试图谱写一场冒险之旅&#xff0c;也许终点只是一场白日梦… 漫漫长路&#xff0c;有人对你微笑过嘛… 全系列文章可参考专栏: Linux实践记录_Once-Day的博客-CSDN博客…

双指针算法第一弹(移动零 复写零 快乐数)

目录 前言 1. 移动零 &#xff08;1&#xff09;题目及示例 &#xff08;2&#xff09;一般思路 &#xff08;3&#xff09;双指针解法 2. 复写零 &#xff08;1&#xff09;题目及示例 &#xff08;2&#xff09;一般解法 &#xff08;3&#xff09;双指针解法 3. 快…

计算机基础知识——C基础+C指针+char类型

指针 这里讲的很细 https://blog.csdn.net/weixin_43624626/article/details/130715839 内存地址&#xff1a;内存中每个字节单位都有一个编号&#xff08;一般用十六进制表示&#xff09; 存储类型 数据类型 *指针变量名&#xff1b;int *p; //定义了一个指针变量p,指向的数…

在Redis中使用Lua脚本实现多条命令的原子性操作

Redis作为一个高性能的键值对数据库&#xff0c;被广泛应用于各种场景。然而&#xff0c;在某些情况下&#xff0c;我们需要执行一系列Redis命令&#xff0c;并确保这些命令的原子性。这时&#xff0c;Lua脚本就成为了一个非常实用的解决方案。 问题的提出 假设我们有一个计数…

【深度学习】图形模型基础(2):概率机器学习模型与人工智能

1.引言 1.1.背景 当机器需要从经验中汲取知识时&#xff0c;概率建模成为了一个至关重要的工具。它不仅为理解学习机制提供了理论框架&#xff0c;而且在实际应用中&#xff0c;特别是在设计能够从数据中学习的机器时&#xff0c;概率建模展现出了其独特的价值。概率框架的核…

Power BI可视化表格矩阵如何保持样式导出数据?

故事背景&#xff1a; 有朋友留言询问&#xff1a;自己从Power BI可视化矩阵表格中导出数据时&#xff0c;导出的表格样式会发生改变&#xff0c;需要线下再手动调整&#xff0c;重新进行透视组合成自己想要的格式。 有没有什么办法让表格导出来跟可视化一样&#xff1f; Po…

汽车电子工程师入门系列——CAN 规范系列通读

我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 屏蔽力是信息过载时代一个人的特殊竞争力,任何消耗你的人和事,多看一眼都是你的不对。非必要不费力证明自己,无利益不试图说服别人,是精神上的节…

SiteSucker Pro for Mac:一键下载整站,轻松备份与离线浏览!

SiteSucker Pro for Mac是一款专为苹果电脑用户设计的网站下载与备份工具&#x1f578;️。它以其强大的整站下载能力和用户友好的界面&#xff0c;成为了众多Mac用户备份网站、离线浏览的得力助手&#x1f4bb;。 这款软件允许用户一键下载整个网站&#xff0c;包括所有的网页…

Docker(八)-Docker运行mysql8容器实例

1.运行mysql8容器实例并挂载数据卷 -e:配置环境变量 --lower_case_table_names1 设置忽略表名大小写一定要放在镜像之后运行mysql8容器实例之前&#xff0c;先查看是否存在mysql8镜像以及是否存在已运行的mysql实例docker run -d -p 3306:3306 --privilegedtrue -v 【宿主机日…

L03_Redis知识图谱

这些知识点你都掌握了吗?大家可以对着问题看下自己掌握程度如何?对于没掌握的知识点,大家自行网上搜索,都会有对应答案,本文不做知识点详细说明,只做简要文字或图示引导。 Redis 全景图 Redis 知识全景图都包括什么呢?简单来说,就是“两大维度,三大主线”。 Redis …

MySQL连接IDEA(Java Web)保姆级教程

第一步&#xff1a;新建项目(File)->Project 第二步&#xff1a;New Project(JDK最好设置1.8版本与数据库适配&#xff0c;详细适配网请到MySQL官网查询MySQL :: MySQL 8.3 Reference Manual :: Search Results) 第三步&#xff1a;点中MySQLTest(项目名)并连续双击shift键-…