首页 > 编程语言 >JUC源码解析:深入解读偏向锁

JUC源码解析:深入解读偏向锁

时间:2024-05-07 14:01:10浏览次数:25  
标签:JUC 存储 对象 lock 源码 线程 轻量级 解析 偏向

JUC源码解析:深入解读偏向锁

本文使用 jdk8

几种锁状态介绍

先介绍一下锁状态吧

img

看偏向锁这一栏, 它的内存存储了 线程ID 和 Epoch , 这一点尤为关键, 意味着偏向锁没有内存可以存储对象头的 hashCode, 而其他锁是有地方存的.。也就意味着,,当锁对象被隐式(父类)或显试调用了 hashcode 时, 就不能进入偏向锁! , 下一节中我会着重介绍

深入偏向锁

  • 偏向锁默认在 jdk 启动几秒后才激活,可以使用JVM参数取消启动延迟:
    • -XX:BiasedLockingStartupDelay=0 为了便于实验,我之后的所有代码都会使用此参数

-XX:-UseBiasedLocking=false 可取消偏向锁

我先来一段代码,用实例看看Mark Word

static Object yyLock = new Object();

public static void main(String[] args) {


    System.out.println("=========== 进入 sync 代码块之前 =========");
    System.out.println(ClassLayout.parseInstance(yyLock).toPrintable());

    synchronized (yyLock) {
        System.out.println("=========== 进入 sync 代码块之后 =========");
        System.out.println(ClassLayout.parseInstance(yyLock).toPrintable());
    }

}

看看结果: 这两段输出分别是进入 sync 前和进入 sync 后的,因为输出太长我做了截选

新对象的mark Word默认就是偏向锁状态, 不过并没有存储偏向线程的id,而只是把值设置为了101

注意观察黄色框框

在进入同步块之前,偏向锁对象没有存储线程id,所以内存是空, 进入同步块之后,偏向锁对象存储了当前的线程ID

线程的id会保存在两个地方:

  • 锁的对象头:存储偏向线程id
  • 线程自己的栈帧,LOCK RECORD:存储自己的id

线程进入同步块时(重点!):会先把锁对象当成偏向锁,检测这两个地方的线程id

  • 如果测试成功,线程会直接获得锁。

  • 如果测试失败,会检查该锁在Mark Word 里是否为101偏向锁:

    • 如果不是偏向锁,CAS竞争锁

    • 如果是偏向锁,则尝试CAS将当前线程作为偏向线程!


值得一提的是,Mark Word中可以看到,偏向锁没有内存空间来保存对象的hashcode

偏向锁中能存储线程id和Epoch,但没有存储hashcode的位置了

所以说,如果一个对象显式或隐式地调用方法生成了 hashcode(),那么,那么偏向锁只能被迫升级为轻量级锁

举一个代码示例:

static Object yyLock = new Object();

public static void main(String[] args) {


    System.out.println("=========== 进入 sync 代码块之前 =========");
    System.out.println(ClassLayout.parseInstance(yyLock).toPrintable());

    // 隐式生成了hashcode
    HashMap map = new HashMap<>();
    map.put(yyLock, "");

    synchronized (yyLock) {
        System.out.println("=========== 进入 sync 代码块之后 =========");
        System.out.println(ClassLayout.parseInstance(yyLock).toPrintable());
    }

}

这时候看看输出:

锁升级了,偏向锁成为了轻量级锁

偏向锁的锁升级

偏向锁使用了一直到竞争出现才会释放锁的机制,一旦出现竞争环境,偏向锁就会升级为轻量级锁

但是

一定会升级吗?一定是竞争才会升级吗?一定会升级为轻量级锁吗?

一、不出现竞争也可能升级

​ 若有锁对象lock、线程A、线程B。线程B要在线程A死亡后执行。

​ 线程A获取lock轻量级锁,lock对象头存储A的线程ID,线程A释放轻量级锁,线程A死亡。lock仍存储着A的线程ID

​ 线程B在线程A死亡后拿到lock偏向锁,lock对象头仍存储着A的线程ID,这时,没有发生锁争抢,但偏向锁lock升级为轻量级锁。

​ 线程B释放lock锁后,lock由轻量级锁释放为无锁状态。

二、偏向锁直接升级为重量级锁

​ 若有锁对象lock,线程A、线程B。

​ 线程A获取lock轻量级锁,线程A还没有释放lock轻量级锁,这时线程B来争抢lock,lock会直接升级成重量级锁。

三、出现竞争时升级为轻量级锁

​ 线程A获取偏向锁,线程A释放了偏向锁,但线程A还没有死亡,这时,线程B来争抢偏向锁,部分偏向锁会升级为轻量级锁、部分偏向锁仍保持偏向锁

只理解为升级为轻量级锁就行了。保持偏向锁的部分建议忽视不做深究。

四、升级为重量级锁后又变成轻量级锁(不建议深究了)

​ 若有锁对象lock,线程A、线程B。

​ 线程A获取lock轻量级锁,线程A还没有释放lock,线程B去争抢lock,这时,lock轻量级锁升级为重量级锁

​ 待线程A、线程B均释放lock且死亡,lock重量级锁变成lock轻量级锁。

偏向锁的撤销

​ 若有偏向锁 lock、线程A、线程B

​ 线程A访问同步块,检查lock对象头中是否存储了A的线程ID,如果有,就获取偏向锁,如果没有,通过CAS自旋将自己的线程ID写入lock MarkWord中。

​ 此时,线程A已经获取到了lock偏向锁,线程B要争抢偏向锁,首先,线程B会检查lock对象头是否存储了自己的线程ID,发现没有存储,尝试CAS替换lock MarkWord,失败了,会发起撤销偏向锁请求。

​ B发起的撤销请求会使线程A暂停,让线程A进入安全点,这时线程A会解锁,线程A移除掉自己栈帧里的 LOCK RECORD(存储线程id),同时删除MarkWord里的线程ID,最后恢复线程。

​ 此时线程B就有机会争抢锁了,会将其争抢为轻量级锁。

批量重偏向 和 批量锁撤销

​ 若有一批以相同Class new 出来的对象作为锁,让线程A、B、C 争抢这一批对象锁

​ A线程获取这一批偏向锁,之后A释放这一批偏向锁,并且A不再是活跃线程。

​ A不再活跃后,有B线程来获取这一批偏向锁,这些偏向锁会升级成轻量级锁。

批量重偏向:

​ 如果B一直获取,达到大约20次阈值,此时会触发批量重偏向,jvm会让之后的锁对象直接偏向线程B,B之后获取到的回是偏向锁。

批量撤销:

​ 在基于批量重偏向的基础上,还在继续进行争抢这批锁达到约40次阈值,并且有第三条线程C加入,这时会触发批量撤销。JVM会标记该Class的对象不能使用偏向锁,以后新创建的对象直接以轻量级锁开始。这是真正完成了锁升级。

真正的锁升级是依赖于 Class 的,而不是依赖于某一个对象的。 发生批量撤销后,使用这个Class new出来的对象,都不能使用偏向锁,而是直接以轻量级锁开始的。

标签:JUC,存储,对象,lock,源码,线程,轻量级,解析,偏向
From: https://www.cnblogs.com/yangruomao/p/18177089

相关文章

  • 在IDEA中加载OpenJDK源码
    之所以要阅读OpenJDK源码,是因为SunJDK的某些源码是缺失的,以JDK1.8为例,sun.reflect,sun.rmi及其子包下的类都是没有源码的。如下以下载OpenJDK1.8源码为例进行说明。下载OpenJDK源码文件,如下载zip格式的压缩包。解压OpenJDK源码压缩包文件,在IDEA中按如下路径加载:【File】......
  • CyberRT_record解析代码走读
    共享内存共享内存可以说是最有用的进程间通信方式,也是最快的IPC形式。两个不同进程A、B共享内存的意思是,同一块物理内存被映射到进程A、B各自的进程地址空间。进程A可以即时看到进程B对共享内存中数据的更新,反之亦然。由于多个进程共享同一块内存区域,必然需要某种同步机......
  • qt 属性控件 使用qt提供的源码 qtpropertybrowser(D:\Qt\5.15.2\Src\qttools\src
    效果:   直接将头文件h和源文件cpp文件添加到项目中。cmakeLists.txt:file(GLOBqtpropertybrowser${QTPROPERTYBROWSER_DIR}/*.cpp${QTPROPERTYBROWSER_DIR}/*.h)include_directories("${QTPROPERTYBROWSER_DIR}")设置了源文件路径 只有一个cpp文件:#includ......
  • [JUCE库]关于JUCE如何生成动态链接库 juce-7.0.1-windows
    前言当我们在使用JUCE库的时候,可能会需要使用到静态链接的方式,还好的一点是JUCE本身提供了CMake编译,也提供了单独的sln编译。本文章仅针对juce-7.0.1-windows,由于不同版本之间差异较大,可能不能通用,但主要的不同点都在修改源码那个环节。编译流程找到源码中提供的编译方案修......
  • Kafka源码分析(四) - Server端-请求处理框架
    系列文章目录https://zhuanlan.zhihu.com/p/367683572一.总体结构先给一张概览图:服务端请求处理过程涉及到两个模块:kafka.network和kafka.server。1.1kafka.network该包是kafka底层模块,提供了服务端NIO通信能力基础。有4个核心类:SocketServer、Acceptor、Processor、Req......
  • 深入解析CSS
    层叠、优先级和继承层叠层叠指的就是这一系列规则。它决定了如何解决css样式规则冲突,是CSS语言的基础。虽然有经验的开发人员对层叠有大体的了解,但是层叠里有些规则还是容易让人误解。当声明冲突时,层叠会依据三种条件解决冲突:样式表的来源:样式是从哪里来的,包括你的样......
  • 光AP测试用例参数解析
    光AP测试用例wireshark抓包/log(射频功率、频宽、协议类型、信道)【概要】AC修改射频配置后,使用wireshark和aplog确认配置成功下发并同步到AP【步骤】Ap后台使用命令【syswan2lanon】映射,wireshark抓以太网包显示频宽:AC下发“configurationupdatarequest”包配置,其中,“message......
  • 深入了解Appium:Capability 高级配置技巧解析
    简介Appium的除了基础的Capability设置,还提供了许多辅助配置项,用于优化自动化测试。这些配置项旨在执行基础配置之外的附加操作。例如:指定设备别名、设备ID或是设置超时时间等,虽然这些不是必需的选项,但是为了实现更高效的测试,通常也建议依据测试的情况适当的添加。xcuites......
  • SSM教务管理系统设计与实现(附源码下载地址)
    @目录01项目背景02使用技术03运行环境04功能分析05数据库设计06项目工程结构07部分功能展示及源码7.1登录页7.2管理员端--首页7.3管理员端--课程管理7.4管理员端--学生管理7.5教师端--首页7.6教师端--个人信息7.7学生端--已修课程7.8学生端--公告管理08运行教程09......
  • Sxstrace.exe 是 Windows 操作系统提供的一个工具,用于跟踪和分析应用程序的依赖项解析
    sxstrace|MicrosoftLearnSxstrace.exe是Windows操作系统提供的一个工具,用于跟踪和分析应用程序的依赖项解析过程。该工具可以帮助用户诊断应用程序启动或运行时出现的依赖项错误或加载问题。在Windows中,许多应用程序依赖于共享组件和库文件,如动态链接库(DLL)。当应用......