JUC:synchronized优化——锁的升级过程(偏向锁->轻量级锁->重量级锁)以及内部实现原理

文章目录

  • 锁的类型
    • 轻量级锁
    • 重量级锁
    • 自旋优化
    • 偏向锁
    • 偏向锁的细节
      • 偏向锁的撤销
      • 批量重偏向
      • 批量撤销
      • 锁消除

锁的类型

重量级锁、轻量级锁、偏向锁。
加锁过程:偏向->轻量级->重量级

轻量级锁

轻量级锁的使用场景:如果一个对象虽然有多线程要加锁,但加锁的时间是错开的(也就是没有竞争),那么可以使用轻量级锁来优化。

轻量级锁对使用者是透明的,即语法仍然是 synchronized,锁的升级什么的不用我们考虑。

static final Object obj = new Object();public static void method1() {synchronized( obj ) {// 同步块 Amethod2();}
}public static void method2() {synchronized( obj ) {// 同步块 B}
}

每一个线程的栈帧中都会包含一个锁记录结构,记录对象头中的markword。

在这里插入图片描述

锁记录中Object reference指向锁对象,并尝试用 cas 交换 Object 的 Mark Word,将Mark Word的值存入锁记录中。

在这里插入图片描述

若cas成功,对象头中存储锁地址和锁状态00(轻量级),表示该线程给此对象加锁。

在这里插入图片描述

如果 cas 失败,有两种情况:

  • 如果是其它线程已经持有了该 Object 的轻量级锁,这时表明有竞争,进入锁膨胀过程。
  • 如果是自己执行了 synchronized 锁重入,那么再添加一条 Lock Record 作为重入的计数。

在这里插入图片描述

当退出 synchronized 代码块(解锁时)如果有取值为 null 的锁记录,表示有重入,这时重置锁记录,表示重入计数减一。

在这里插入图片描述

当退出 synchronized 代码块(解锁时)锁记录的值不为 null,这时使用 cas 将 Mark Word 的值恢复给对象头

  • 成功,则解锁成功。
  • 失败,说明轻量级锁进行了锁膨胀或已经升级为重量级锁,进入重量级锁解锁流程。

重量级锁

如果在尝试加轻量级锁的过程中,CAS操作无法成功,这时一种情况就是有其它线程为此对象加上了轻量级锁(有竞争),这时会进行锁膨胀,将轻量级锁膨胀为重量级锁。

static Object obj = new Object();
public static void method1() {synchronized( obj ) {// 同步块}
}

当 Thread-1 进行轻量级加锁时,Thread-0 已经对该对象加了轻量级锁。

在这里插入图片描述

这时Thread-1就会CAS失败,进入锁膨胀过程。

  • Object 对象申请 Monitor 锁,让 Object 指向重量级锁地址
  • 然后自己进入 Monitor 的 EntryList BLOCKED
  • Object 对象头锁标记变为10(重量级)

在这里插入图片描述

当 Thread-0 退出同步块解锁时,使用 CAS 将 Mark Word 的值恢复给对象头,失败。这时会进入重量级解锁流程,即按照 Monitor 地址找到 Monitor 对象,设置 Owner 为 null,唤醒 EntryList 中 BLOCKED 线程。

简单来说,我们可以简化一下:

  • 就是没竞争时,自己正常加锁运行解锁即可,同一线程有锁的重入,防止没必要的加锁解锁过程。
  • 如果有竞争时,新线程进入阻塞队列等待,当解锁时,唤醒阻塞队列中的线程。

自旋优化

重量级锁竞争的时候,可以使用自旋(不断的循环尝试获取重量级锁)来进行优化,如果当前线程自旋成功(当前持有锁的线程释放了锁),这时当前线程就可以避免阻塞。(阻塞再恢复,会进行上下文切换,耗费性能)

但是自旋也是会消耗性能的,尤其是进程非常多的时候,一堆进程在不断循环等待锁被释放,优点就是及时,一释放锁就能感应到。

偏向锁

在锁重入时,会产生锁记录,每次都需要尝试CAS,虽然会失败,但是CAS这个操作是没必要的。

优化:

将对象头中记录持有该锁的线程id,如果发生锁的重入,检测对象头,若是自己的,那么就不用再CAS了。

对比:

在这里插入图片描述

在这里插入图片描述

偏向锁的细节

在这里插入图片描述

一个对象创建时:

  • 如果开启了偏向锁(默认开启),那么对象创建后,markword 值为 0x05 即最后 3 位为 101,这时它的 thread、epoch、age 都为 0。

  • 偏向锁是默认是延迟的,不会在程序启动时立即生效,如果想避免延迟,可以加 VM 参数 -XX:BiasedLockingStartupDelay=0 来禁用延迟。

  • 如果没有开启偏向锁,那么对象创建后,markword 值为 0x01 即最后 3 位为 001,这时它的 hashcode、age 都为 0,第一次用到 hashcode 时才会赋值。

  • 正常状态对象一开始是没有 hashCode 的,第一次调用才生成。

  • 调用了 hashCode() 后会撤销该对象的偏向锁。

从图中也可以看出,偏向锁的markword没有位置可以放hashcode了,除非转变为轻量级锁,所以调用对象的hashCode会撤销他的偏向锁。

偏向锁的撤销

除了上面说的调用hashCode,也可用 让其他线程使用偏向锁,这时偏向锁会升级为普通锁。

不过两个线程需要错开加锁,不然会升级为重量级锁。

批量重偏向

可用发现,就算错开没用竞争,那么锁也会从偏向锁转变为普通锁,那其实锁的转变也是消耗性能的,所以就有了偏向锁的重偏向这个功能。

如果对象被多个线程访问,但没有竞争(错开),这时偏向了线程 T1 的对象仍有机会重新偏向 T2,重偏向会重置对象的 Thread ID 为 T2线程。

当某对象(这一类的不同对象)撤销偏向锁阈值超过20次后,jvm会觉得,我是不是偏向错了,于是会在给这些对象加锁的时候重新偏向至新加锁线程。

批量撤销

当相同类型不同对象撤销偏向锁阈值超过 40 次后,jvm 会这样觉得,自己确实偏向错了,根本就不该偏向,这个锁竞争太激烈了。于是整个类的所有对象都会变为不可偏向的,新建的该类型对象也是不可偏向的(轻量级)。

锁消除

简单来说,当jvm调用某方法达到一定阈值,就会用记时编译器优化,发现锁加和不加没有影响时,就会去除掉锁。

public class MyBenchmark {static int x = 0;public void a() throws Exception {x++;}public void b() throws Exception {Object o = new Object();synchronized (o) {x++;}}
}

比如Object为局部变量,根本不会逃离此方法作用范围,不能被共享,那加不加锁没啥意义,jvm就会去除掉锁去运行,从而优化性能。

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

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

相关文章

数据结构与算法(2)顺序表的初始化、插入、打印、删除、查找元素——C语言版

目录 1.前言 2.头文件的定义 3.菜单栏的设置 4.顺序表的初始化 5.添加元素 6.打印元素 7.查找元素 8.删除元素 9.插入元素 10.主函数 11.完整的代码实现 1.前言 数据结构包括三个方面 逻辑结构存储结构运算 而线性表有两种典型的存储结构 顺序存储结构链式存储结构 具体关系可…

提取gdip-yolo与ia-seg中的图像自适应模块进行图像去雾与亮度增强

gdip-yolo与ia-seg都是一种将图像自适应模块插入模型前面,从而提升模型在特定数据下检测能力的网络结构。gdip-yolo提出了gdip模块,可以应用到大雾数据与低亮度数据(夜晚环境),然后用于目标检测训练;ia-seg将ia-yolo中的代码修改了一下修车了ipam模块,应用到低亮度数据(…

在线随机密码生成器源码

纯HTML,该去的已去掉,该简化的简化,最高支持32位混合随机密码生成。 源码下载:在线随机密码生成器源码

Golang生成UUID

安装依赖 go get -u github.com/google/uuid文档 谷歌UUID文档 示例 函数签名func NewV7() ( UUID ,错误) func (receiver *basicUtils) GenerateUUID() uuid.UUID {return uuid.Must(uuid.NewV7()) } uid : GenerateUUID()

RH850从0搭建Autosar开发环境【3X】- Davinci Configurator之RTE模块配置详解(上)

RTE模块配置详解 - 上 一、RTE模块配置实操1.1 打开RTE模块1.2 RTE模块错误消除在这里插入图片描述 这里我们利用工具的自动处理功能。二、Configurator工具Validation总结本节我们就手把手详解RTE配置实现,其实也没有什么过多的操作。。。这个模块更多是工具自动处理的。 一、…

Dockerfile和Docker-compose

一、概述 Dockerfile和Docker Compose是用于构建和管理 Docker 容器的两个工具,但它们的作用和使用方式不同。 Dockerfile Dockerfile 是一个文本文件,用于定义 Docker 镜像的构建规则。它包含一系列指令,如 FROM(指定基础镜像…

吴恩达2022机器学习专项课程(一) 4.3 梯度下降的直观理解

问题预览/关键词 本节内容是?J对w求导的含义是?如何确定切线的方向?w在函数J递增处的切线方向是?导数项为正数,w和函数J的关系是?w在函数J递减处的切线方向是?导数项为负数,w和函数…

ES6中的Set集合

Set集合 ES6 提供了新的数据结构Set(集合)。 它类似于数组,但成员的值都是唯一的集合实现了 iterator 接口,所以可以使用「扩展运算符」和[for…of…」进行遍历集合的属性和方法 集合的属性和方法: 1)size,返回集合的元…

bizcharts中LineChart时间戳使用moment转化出现Invalid Date

文章目录 一、前言1.1、问题1.2、解决 二、bizcharts三、moment.js四、在线源码五、最后 一、前言 1.1、问题 最近在使用bizcharts绘制折线图LineChart的时候,发现X轴的时间显示成了Invalid Date。如下图所示: 发现是后端返回了时间戳字符串"1572…

matlab 复制点云

目录 一、概述1、算法概述2、主要函数3、参考文献二、代码实现三、结果展示四、参考链接本文由CSDN点云侠原创,原文链接。如果你不是在点云侠的博客中看到该文章,那么此处便是不要脸的爬虫。 一、概述 1、算法概述

OpenAI发布Voice Engine模型!用AI合成你的声音!

大家好,我是木易,一个持续关注AI领域的互联网技术产品经理,国内Top2本科,美国Top10 CS研究生,MBA。我坚信AI是普通人变强的“外挂”,所以创建了“AI信息Gap”这个公众号,专注于分享AI全维度知识…

写作类AI推荐(二)

本章要介绍的写作AI如下: 火山写作 主要功能: AI智能创作:告诉 AI 你想写什么,立即生成你理想中的文章AI智能改写:选中段落句子,可提升表达、修改语气、扩写、总结、缩写等文章内容优化:根据全文…