首页 > 其他分享 >Sychronized 原理,锁升级优化

Sychronized 原理,锁升级优化

时间:2023-07-31 23:36:43浏览次数:30  
标签:Monitor 对象 升级 Record MarkWord 线程 Sychronized 优化 轻量级

Java 对象头

以 32 位虚拟机为例

普通对象

所以以 Integer 和 int 为例子

  • Integer 8字节对象头 + 4字节 int 值,所以大小是 int 的 3 倍
  • int 4字节 int 值

数组对象

如 Student[] s = new Student[8],还包括数组长度 length

其中 markword 结构为

Mark Word被设计成一个非固定的动态数据结构,以便在极小的空间内存储尽量多的信息。它会根据对象的状态复用自己的存储空间。例如在32位的HotSpot虚拟机中:

  • 对象未被锁定的状态下, Mark Word的32个比特空间里的25个比特将用于存储对象哈希码 hashcode,4个比特用于存储对象分代年龄 age,2 个比特用于存储锁标志位,还有1个比特固定为0(这表示未进入偏向模式)。
  • 进入 Monitor 之后,即重量级锁状态 10 后,ptr_to_heavyweigth_monitor 指向 monitor 对象(monitor 对象是由操作系统提供的?)。hashcode,age 这些会被被指向 monitor 的指针覆盖,它们会被暂存在 monitor 中,monitor 结束后会把它们还原。

对象除了未被锁定的正常状态外,还有轻量级锁定、重量级锁定、GC标记、可偏向等几种不同状态。

 64 位虚拟机 Mark Word

 

Monitor 对象(重量级锁)

每个 Java 对象都可以关联一个 Monitor 对象,如果使用 synchronized 给对象上锁(重量级)之后,该对象头的 Mark Word 中就被设置指向 Monitor 对象的指针

Monitor 对象结构如下

  • 刚开始 Monitor 中 Owner 为 null
  • 当 Thread-2 执行 synchronized(obj) 就会将 Monitor 的所有者 Owner 置为 Thread-2,Monitor中只能有一 个 Owner
  • 在 Thread-2 上锁的过程中,如果 Thread-3,Thread-4,Thread-5 也来执行 synchronized(obj),就会进入 EntryList BLOCKED
  • Thread-2 执行完同步代码块的内容,然后唤醒 EntryList 中等待的线程来竞争锁,竞争的时是非公平的
  • 图中 WaitSet 中的 Thread-0,Thread-1 是之前获得过锁,但条件不满足进入 WAITING 状态的线程,后面讲 wait-notify 时会分析

注意:

  • synchronized 必须是进入同一个对象的 monitor 才有上述的效果
  • 不加 synchronized 的对象不会关联监视器,不遵从以上规则

sychronized 字节码

static final Object lock = new Object();
  static int counter = 0;
  public static void main(String[] args) {
     synchronized (lock) {
       counter++;
   }
}

上面这段代码对应的字节码为

  • 同步代码:通过 moniterenter、moniterexit 关联到到一个monitor对象,进入时设置Owner为当前线程,计数+1、退出-1。除了正常出口的 monitorexit,还在异常处理代码里插入了 monitorexit。
  • 实例方法:隐式调用moniterenter、moniterexit
  • 静态方法:隐式调用moniterenter、moniterexit
public static void main(java.lang.String[]);
 descriptor: ([Ljava/lang/String;)V
 flags: ACC_PUBLIC, ACC_STATIC
Code:
 stack=2, locals=3, args_size=1
 0: getstatic #2 // <- lock引用 (synchronized开始)
 3: dup
 4: astore_1 // lock引用 -> 暂存到 slot 1
 5: monitorenter // 加锁。将 lock对象 MarkWord 置为 Monitor 指针,覆盖掉原来的 hashcode,age(暂存到了 monitor 中)
 6: getstatic #3 // <- i
 9: iconst_1 // 准备常数 1
 10: iadd // +1
 11: putstatic #3 // -> i
 14: aload_1 // <- lock引用
 15: monitorexit // 解锁。将 lock对象 MarkWord 重置为 hashcode,age, 唤醒 EntryList
 16: goto 24
 19: astore_2 // 同步代码块发生异常,会走这一块进行异常处理。e -> slot 2
 20: aload_1 // <- lock引用
 21: monitorexit // 异常处理中进行解锁。将 lock对象 MarkWord 重置, 唤醒 EntryList
 22: aload_2 // <- slot 2 (e)
 23: athrow // throw e
 24: return
 Exception table:
   from to target type
   6    16 19 any
   19   22 19 any
 LineNumberTable:
 line 8: 0
 line 9: 6
 line 10: 14
 line 11: 24
 LocalVariableTable:
 Start Length Slot Name Signature
 0 25 0 args [Ljava/lang/String;
 StackMapTable: number_of_entries = 2
 frame_type = 255 /* full_frame */
 offset_delta = 19
 locals = [ class "[Ljava/lang/String;", class java/lang/Object ]
 stack = [ class java/lang/Throwable ]
 frame_type = 250 /* chop */
 offset_delta = 4

 

 

轻量级锁(Lock Record)

monitor 是操作系统提供的对象,每次使用它成本比较高,Java的线程是映射到操作系统的原生内核线程之上的,如果要阻塞或唤醒一条线程,则需要操作系统来帮忙完成,这就不可避免地陷入用户态到核心态的转换中,进行这种状态转 换需要耗费很多的处理器时间。

轻量级锁的使用场景:如果一个对象虽然有多线程要加锁,但加锁的时间是错开的(也就是没有竞争,一个加锁解锁完了另一个才过来),那么可以 使用轻量级锁来优化。 轻量级锁对使用者是透明的,即语法仍然是 synchronized。意义在于 只有少量竞争时,不用创建 Monitor (操作系统互斥量,开销较大)。但是如果多个线程在同一时刻进入临界区,会导致轻量级锁膨胀升级重量级锁,所以轻量级锁的出现并非是要替代重量级锁。

  1. 创建锁记录(Lock Record)对象:每个线程的栈帧都会包含一个锁记录的结构,内部可以存储锁定对象的 MarkWord
  2. 判断 MarkWord 是否为无锁状态,标识位001;如果 MarkWord 处于无锁状态,现在对象的无锁状态 MarkWord(存的是hashcode age)暂存到 Lock Record 的 Displaced Mark Word 中。
  3. 通过 CAS 尝试将 对象头的无锁 MarkWord 更新为指向 Lock Record 对象的指针
  4. 如果更新成功,表示竞争到锁, MarkWord 状态转为 00 (轻量级锁),然后执行同步代码。
  5. 如果更新失败:
    • 其它线程竞争(MarkWord 指向其它线程):表明有多个线程竞争轻量级锁,轻量级锁需要膨胀升级为重量级锁。去 Monitor 的 EntryList 等待去了。
    • 同一线程锁重入(MarkWord 指向当前线程):那么再添加一条 Lock Record 作为重入的计数(但是 Lock Record 的 Displaced Mark Word为 null)。
  6. 当退出 synchronized 代码块(解锁时)
    • 如果有 Displaced Mark Word 为 null 的锁记录,表示有重入,这时重置取值为 null 的这个锁记录,表示重入计数减一
    • 锁记录 Displaced Mark Word 不为 null,这时使用 cas 将锁记录中的 Dispalced Mark Word 即原来无锁状态的 Mark Word 恢复给对象头
      • 成功,则解锁成功
      • 失败,说明轻量级锁进行了锁膨胀或已经升级为重量级锁,进入重量级锁解锁流程

为什么JVM选择在线程栈中添加Displaced Mark word为null的Lock Record来表示重入计数呢?

首先锁重入次数是一定要记录下来的,因为每次解锁都需要对应一次加锁,解锁次数等于加锁次数时,该锁才真正的被释放,也就是在解锁时需要用到说锁重入次数的。

一个简单的方案是将锁重入次数记录在对象头的mark word中,但mark word的大小是有限的,已经存放不下该信息了。

另一个方案是只创建一个Lock Record并在其中记录重入次数,Hotspot没有这样做的原因我猜是考虑到效率有影响:每次重入获得锁都需要遍历该线程的栈找到对应的Lock Record,然后修改它的值。

所以最终Hotspot选择每次获得锁都添加一个Lock Record来表示锁的重入。

  

 

锁膨胀

轻量级锁时 Thread -1 尝试 CAS 将无锁的 MarkWord 更新为指向 Lock Record 对象的指针:如果更新失败,并且不是锁重入,即 MarkWord 指向的非当前线程。

说明是有其它线程为此对象加上了轻量级锁,发生了锁竞争。这时需要进行锁膨胀,将轻量级锁变为重量级锁。

锁膨胀流程

  • 为 Object 对象申请 Monitor 锁,让 Object 指向重量级锁地址
  • 然后自己进入 Monitor 的 EntryList BLOCKED

当 Thread-0 退出同步块解锁时,使用 cas 将 MarkWord 的值恢复给对象头(本应指向自己这个 Lock Record 但现在指向了 Monitor),失败。这时会进入重量级解锁 流程,即按照 Monitor 地址找到 Monitor 对象,设置 Owner 为 null,唤醒 EntryList 中 BLOCKED 线程

 

自旋优化

自选:让线程先不要进入阻塞,而是进行几次循环。因为阻塞意味着线程会发生一次上下文切换

重量级锁竞争的时候,还可以使用自旋来进行优化,如果当前线程自旋成功(即这时候持锁线程已经退出了同步块,释放了锁),这时当前线程就可以避免阻塞。

  • 自旋会占用 CPU 时间,单核 CPU 自旋就是浪费,多核 CPU 自旋才能发挥优势。
  • 在 Java 6 之后自旋锁是自适应的,比如对象刚刚的一次自旋操作成功过,那么认为这次自旋成功的可能性会高,就多自旋几次;反之,就少自旋甚至不自旋,总之,比较智能。
  • Java 7 之后不能控制是否开启自旋功能

自旋重试成功的情况

自旋重试成功的情况

 

偏向锁

参考:

黑马程序员:深入学习Java并发编程,JUC并发编程全套教程

有赞:Java锁与线程的那些事

死磕Synchronized底层实现--概论

死磕Synchronized底层实现--偏向锁

死磕Synchronized底层实现--轻量级锁

死磕Synchronized底层实现--重量级锁

标签:Monitor,对象,升级,Record,MarkWord,线程,Sychronized,优化,轻量级
From: https://www.cnblogs.com/suBlog/p/17595294.html

相关文章

  • Java面试题 P27:数据库篇:MySql篇-MySql优化-Sql语句执行很慢,如何分析呢?
       ......
  • Java面试题 P26:数据库篇:MySql篇-MySql优化-如何定位慢查询?
          ......
  • mysql优化--索引
    mysql优化--索引Mysql索引大概有五种类型:普通索引(INDEX):最基本的索引,没有任何限制唯一索引(UNIQUE):与"普通索引"类似,不同的就是:索引列的值必须唯一,但允许有空值。主键索引(PRIMARY):它是一种特殊的唯一索引,不允许有空值。全文索引(FULLTEXT):可用于MyISAM表,mysql5.6之后也可......
  • 深入理解Java虚拟机(JVM):原理、结构与性能优化
    1.介绍Java虚拟机(JVM)是Java程序的核心执行引擎,负责将Java源代码编译成可执行的字节码,并在运行时负责解释执行字节码或将其编译成本地机器代码。本文将深入探讨JVM的原理、结构以及性能优化的相关技术。2.JVM原理与结构2.1JVM运行时数据区域JVM运行时数据区域由以下几部分组......
  • zynq在线升级系统
    1. 升级方法   (1)用winscp连接板卡,进入目录  /home/petalinux 下;   (2)将升级文件夹sys_update复制到该目录中,替换原文件(原文件没有也无所谓),并sync一下;       注意:上位机文件传输太慢时通过调试终端发送ethtool-seth2autonegon指令。   ......
  • Flutter升级Gradle和Gradle Plugin
    Flutter升级Gradle和GradlePlugin目前Flutter3.0默认的Gradle版本是7.5,当然也有可能会更早一些。有时候因为某些需求要升级Gradle到新版本。本文以升级到8.2.1版本举例,会讲述Flutter项目中升级Gradle和GradlePlugin以及androidtools、设置镜像源等问题。......
  • Matlab软件许可证优化成功案例
    引言:在当今数字化时代,各种软件应用已经成为企业运营的必需品。然而,购买正版软件许可证往往需要大量的资金,这对于许多企业来说是一个巨大的挑战。为了解决这个问题,许多企业选择了浮点许可优化管理软件,帮助企业更灵活地使用浮动软件许可证,从而降低成本。本文将通过一个真实的案例,展示......
  • 美颜SDK开发指南:优化直播人像表现
    在当今社交媒体和直播平台的流行趋势下,越来越多的用户渴望在直播中展现更加自信和美丽的一面。本文将探讨美颜SDK的开发指南,介绍其优化直播人像表现的重要性以及关键的技术要点。 一、用户为什么离不开美颜?美颜SDK作为一种集成在直播应用中的技术工具,可以实时对主播的人像进行美化......
  • Tita 升级|「任务」相关功能优化升级
    1.移动端添加任务时,支持选择任务分类Tita-OKR和新绩效一体化管理平台使用场景:需要对任务进行分类,便于按不同类别进行追踪进度tips:使用任务分类功能需要联系tita售后顾问开启2.任务定量统计表增加工作表2,项目和任务合并到一行展示使用场景:之前的表格项目和任务以层级方......
  • 山特一体化解决方案,助力电子政务中心机房轻盈升级
    随着市民对便捷、高效的政务服务需求不断增加,电子政务已成为服务型政府建设的重要标志,由此带来的庞大业务流及信息流,都给支撑其可靠运行的电子政务中心机房带来了诸多挑战。山特作为全方位电源解决方案提供商,在政府行业市场,有着丰富的项目经验与技术沉淀,始终致力于帮助用户数......