商城首页欢迎来到中国正版软件门户

您的位置:首页 >如何通过分析 Synchronized 的锁膨胀机制理解从偏向锁到重量级锁的位状态迁移

如何通过分析 Synchronized 的锁膨胀机制理解从偏向锁到重量级锁的位状态迁移

  发布于2026-04-28 阅读(0)

扫一扫,手机访问

如何通过分析 Synchronized 的锁膨胀机制理解从偏向锁到重量级锁的位状态迁移

如何通过分析 Synchronized 的锁膨胀机制理解从偏向锁到重量级锁的位状态迁移

简单来说,锁的状态就藏在对象头的标记位里:偏向锁的Mark Word低3位是101,轻量级锁是000,而重量级锁则是010。识别这些位模式,并理解它们之间不可逆的迁移路径,是掌握Synchronized底层机制的关键。当然,这个过程不能光靠理论推演,必须结合字节码分析、JOL工具输出和特定的JVM参数来交叉验证。

偏向锁的 Mark Word 位模式怎么识别

偏向锁的实现,绕开了操作系统的互斥量,其奥秘全在对象头那个小小的Mark Word里。具体来说,它依赖里面的线程ID和epoch字段来做标记。当一个对象尚未被任何锁定时,它的Mark Word低3位显示为001,代表无锁状态。一旦JVM启用了偏向锁优化,并且这个对象第一次被某个线程获取,JVM就会通过CAS操作,把当前线程的ID写入Mark Word的高位区域,同时把低3位从001改为101——这个101就是偏向锁独一无二的身份证。

不过,在实际观察时,有几个坑很容易踩到:

  • 直接去读Mark Word的十六进制值,却忽略了JVM在内存中的大端/小端布局,导致位解析完全错位。
  • 在已经通过-XX:-UseBiasedLocking参数全局禁用偏向锁的环境里,还试图寻找偏向锁的行为,自然是徒劳无功。
  • 使用JOL(Ja va Object Layout)这个神器来查看对象布局时,如果没加上-XX:BiasedLockingStartupDelay=0参数,那么JVM默认会有4秒的延迟,在这期间创建的对象是不会进入偏向状态的。

轻量级锁触发时 Mark Word 怎么变化

天下没有不散的筵席,偏向锁的“独占”状态也是。当另一个线程也来尝试获取这个已经被偏向的锁时,故事就进入了下一章。JVM会首先执行偏向锁撤销(revoke),然后尝试让竞争线程走轻量级锁的流程。这个过程的核心,依然是CAS:线程会试图将自己栈帧中的锁记录(Lock Record)地址写入对象的Mark Word。如果成功,Mark Word的低3位就会从101变成000,这个000正是轻量级锁的标志。此时,对象头里存储的不再是线程ID,而是一个指向线程栈空间的指针。

这里有几个关键细节值得玩味:

  • CAS失败并不意味着锁会立刻膨胀升级。失败后,线程会进入自旋(默认10次,可通过-XX:PreBlockSpin调整),在自旋期间,Mark Word依然保持着000的轻量级锁状态。
  • 如果在自旋过程中,原来持有锁的线程恰好退出了同步块,锁会被释放,Mark Word会恢复为无锁的001。这时,自旋的线程就有机会重新竞争,甚至可能再次让对象偏向自己。
  • 只有当自旋耗尽,锁依然没能获取成功,才会真正触发锁膨胀。这时,Mark Word的内容会被替换为指向一个重量级ObjectMonitor结构的指针,其低3位也随之变为010

重量级锁的 010 标识为什么不可逆

当你在对象头里看到010这个三位标识时,就意味着对象已经进入了重量级锁的领域。此时,Mark Word里存储的是一个指向ObjectMonitor结构的指针。这个结构由JVM在堆上专门分配,并且与操作系统底层的mutex(互斥量)相关联,因此锁操作会涉及用户态到内核态的切换,开销自然就上去了。

那么,为什么说这个过程是不可逆的呢?锁一旦升级为重量级,就再也回不去了,这背后有几个根本原因:

  • ObjectMonitor结构一旦创建,其内部维护的队列(如_EntryList_WaitSet)可能已经挂载了正在阻塞等待的线程。如果此时降级,这些线程的等待状态和唤醒语义将无法得到保证,会引发混乱。
  • 偏向锁和轻量级锁的生命周期与线程的栈帧紧密绑定。而重量级锁的ObjectMonitor是独立于栈帧存在的堆对象,管理域完全不同,技术上无法安全地回退到依赖栈帧的轻量级状态。
  • 从JVM源码的实现来看,锁膨胀(inflate)是一个单向的构造过程,并没有设计对应的收索(deflate)逻辑来支持降级。

怎么验证锁状态迁移过程

理论说得再透彻,不如动手验证一遍。要真正看清锁状态的迁移,不能依赖猜测或片面的日志,必须进行交叉验证,主要从三个维度入手:字节码、对象头实际数据和JVM运行时参数。

  • 首先,用ja vap -v命令反编译你的Class文件,确认同步块是否确实生成了monitorentermonitorexit指令。
  • 接着,借助JOL工具,通过ClassLayout.parseInstance(obj).toPrintable()实时打印出对象的布局,特别是Mark Word的十六进制值。然后,你需要对照JVM源码(比如markOop.hpp文件)中的位定义表,像解码密码一样解析出每一位的含义。
  • 此外,可以加上-XX:+PrintSafepointStatistics -XX:PrintSafepointStatisticsCount=1这样的JVM参数,观察偏向锁撤销时是否会引发全局安全点(Safepoint),这对理解性能影响很有帮助。
  • 最后,注意避开一些“陷阱”对象。比如容器类(HashMap)或字符串常量池中的对象,它们内部可能默认禁用了偏向锁,在这上面测试会得不到预期的结果。

说到底,最容易忽略的一个核心点是:锁膨胀并非发生在你调用synchronized的那一刻,而是在后续线程**竞争获取锁失败时**才被触发。而且,所有的状态变更都是以单个对象为粒度的,这意味着同一个类的两个不同实例,完全可能处于截然不同的锁状态——一个可能还是无锁,另一个可能已经膨胀为重量级锁了。理解这一点,才能算真正摸清了Synchronized的脾气。

本文转载于:https://www.php.cn/faq/2378125.html 如有侵犯,请联系zhengruancom@outlook.com删除。
免责声明:正软商城发布此文仅为传递信息,不代表正软商城认同其观点或证实其描述。

热门关注