Java锁机制之轻量级锁判断与尝试逻辑源码剖析

张开发
2026/6/6 12:57:36 15 分钟阅读

分享文章

Java锁机制之轻量级锁判断与尝试逻辑源码剖析
轻量级锁判断与尝试逻辑源码剖析前言轻量级锁判断与尝试逻辑源码剖析1. 核心源码注释解析 (synchronizer.cpp)1.1 ObjectSynchronizer::fast_enter1.2 ObjectSynchronizer::slow_enter2. JVM 判断与尝试轻量级锁的详细图解流程第一步状态前置检查偏向锁拦截第二步无锁状态下的 CAS 抢占轻量级锁核心第三步重入性判定轻量级锁重入3. 对象头Mark Word状态转换临界点总结前言本文旨在记录近期研读Java源码的学习心得与疑难问题。由于个人理解水平有限文中内容难免存在疏漏恳请读者不吝指正。轻量级锁判断与尝试逻辑源码剖析在 OpenJDK 8 的 HotSpot 虚拟机中对象同步synchronized的入口核心逻辑位于hotspot/src/share/vm/runtime/synchronizer.cpp。虽然用户通常关注ObjectSynchronizer::fast_enter但实际上fast_enter主要负责处理偏向锁Biased Locking。如果偏向锁未开启、获取失败或被撤销JVM 会立即调用ObjectSynchronizer::slow_enter。而轻量级锁Lightweight Locking的判断与尝试正是slow_enter的核心职责。只有当轻量级锁尝试失败或发生锁竞争时JVM 才会调用ObjectSynchronizer::inflate膨胀为重量级锁。以下是完整的执行流、源码及深度注释解析。1. 核心源码注释解析 (synchronizer.cpp)1.1 ObjectSynchronizer::fast_enter这是 Java 字节码monitorenter在运行时的第一站。voidObjectSynchronizer::fast_enter(Handle obj,BasicLock*lock,boolattempt_rebias,TRAPS){// 1. 判断是否开启了偏向锁虚拟机参数 (-XX:UseBiasedLocking)if(UseBiasedLocking){if(!SafetyChecks){// 如果不在安全点进行一些必要的安全检查runtime_verify_locks();}// 尝试获取偏向锁或进行偏向锁撤销BiasedLocking::Condition condBiasedLocking::revoke_and_rebias(obj,attempt_rebias,THREAD);// 如果成功获取了偏向锁或成功重偏向则直接返回不再向下执行轻量级锁逻辑if(condBiasedLocking::BIAS_REVOKED_AND_REBIASED){return;}}// 2. 如果偏向锁未开启、或者偏向锁尝试失败/被撤销则进入 slow_enter// 核心的“轻量级锁”尝试与判断全在这里面slow_enter(obj,lock,THREAD);}1.2 ObjectSynchronizer::slow_enter在这个方法中JVM 正式开始判断并尝试进行轻量级锁的获取。voidObjectSynchronizer::slow_enter(Handle obj,BasicLock*lock,TRAPS){// 读取对象头中的 Mark WordmarkOop markobj-mark();// 断言此时 Mark Word 的偏向标记位绝对不应该为 1 (已经在 fast_enter 或撤销流程中处理完毕)assert(!mark-has_bias_pattern(),should not see bias pattern here);// ------------------------------------------------------------------// 核心判断一判断目标对象是否处于“无锁状态”Neutral State// 无锁状态的特征Mark Word 后三位为 001即 hash:25 | age:4 | biased_lock:0 | lock:01// ------------------------------------------------------------------if(mark-is_neutral()){// 【尝试轻量级锁步骤 A】在当前线程的栈帧Stack Frame中构建一个锁记录BasicLock// 将当前无锁对象的 Mark Word 复制到线程栈的 displaced_header 中保存lock-set_displaced_header(mark);// 【尝试轻量级锁步骤 B】利用 CAS 尝试将对象头Mark Word替换为指向当前线程栈中 BasicLock 的指针// 如果成功说明没有竞争当前线程成功升级并持有该对象的轻量级锁if(mark(markOop)Atomic::cmpxchg_ptr(lock,obj()-mark_addr(),mark)){TEVENT(slow_enter:fast path success);return;// 成功获取轻量级锁直接返回避免了重量级锁的开销}// 如果 CAS 失败说明在此期间有其他线程竞争或者状态发生改变直接落入下方进行膨胀// ------------------------------------------------------------------// 核心判断二判断是否为“锁重入”Reentrant Locking// 如果对象已经处于轻量级锁状态后两位为 00且该锁指针指向当前线程的栈范围内// ------------------------------------------------------------------}elseif(mark-has_locker()THREAD-is_lock_owned((address)mark-locker())){// 断言检查确保不是重复锁同一个 BasicLock 实例assert(lock!mark-locker(),must not re-lock the same BasicLock);assert(lock-displaced_header()-is_neutral(),must be neutral);// 【锁重入优化】对于轻量级锁的重入HotSpot 采用将 displaced_header 设置为 NULL 的简便标记法// 解锁时如果发现是 NULL说明是重入释放直接弹栈即可无需写回对象头lock-set_displaced_header(NULL);return;// 成功处理轻量级锁重入返回}// ------------------------------------------------------------------// 走到这里说明上述轻量级锁的尝试【全部失败】// 1. 对象非无锁状态可能已经是重量级锁或者已被其他线程抢占为轻量级锁// 2. 发生竞争CAS 替换失败// 3. 也不是当前线程的锁重入// ------------------------------------------------------------------// 在膨胀前出于安全考虑将当前栈中锁记录的 displaced_header 设置为一个不可用的标记位lock-set_displaced_header(markOopDesc::unused_mark());// 【激进化/膨胀】正式调用 inflate 方法将轻量级锁升级为重量级锁ObjectMonitor// 并调用 enter() 让当前线程进入 Monitor 的等待队列或自旋等待ObjectSynchronizer::inflate(THREAD,obj())-enter(THREAD);}2. JVM 判断与尝试轻量级锁的详细图解流程根据上述源码JVM 在决定动用重量级锁ObjectMonitor之前有着非常严密的三步式判断逻辑第一步状态前置检查偏向锁拦截在fast_enter中JVM 首先检查-XX:UseBiasedLocking是否开启。如果开启JVM 试图通过无因果关系的指针更替CAS 线程 ID来实现偏向锁。如果发现偏向锁失效比如其他线程曾持有过该锁导致偏向被撤销或者发生竞争导致撤销JVM 会清理掉偏向标记将 Mark Word 的最后 3 位从101变更为001随后将其送入slow_enter。第二步无锁状态下的 CAS 抢占轻量级锁核心进入slow_enter后第一件事就是通过mark-is_neutral()判断最后两位是否为01。为什么是无锁才尝试轻量级锁因为轻量级锁的设计初衷是假设“在绝大多数情况下同步块内不存在实质性的竞争”。如何尝试1. 线程在自己的栈帧中分配一个BasicLock锁记录空间。把对象当前的Mark Word备份到BasicLock的displaced_header中这是为了在未来解锁时能够完美还原对象头。执行Atomic::cmpxchg_ptr原子指令。这一步试图将对象头的Mark Word刷新为指向该线程栈中 BasicLock 的物理内存地址同时锁标志位变成00轻量级锁状态。结果导向如果成功就此返回整个加锁过程只消耗了一次内存复制和一次原子 CAS 指令完美避开了操作系统级别的互斥量Mutex。第三步重入性判定轻量级锁重入如果第二步的 CAS 失败了JVM 并不会马上升级重量级锁它会进一步求证“这个锁是不是我自己持有的”通过mark-has_locker()确认对象头当前是否正处于轻量级锁状态后两位为00。通过THREAD-is_lock_owned((address)mark-locker())判定对象头里存储的那个指针地址是否落在了当前线程的栈空间最高点和最低点之间。如果都在范围内说明是当前线程发生了锁重入。JVM 此时只需向栈中再压入一个displaced_header为NULL的BasicLock即可宣告轻量级锁重入成功。3. 对象头Mark Word状态转换临界点为了彻底理解源码中的is_neutral()和状态转换以下是 HotSpot 中 Mark Word 在各个阶段的位分布特征以 64 位虚拟机为例锁状态25bit31bit1bit (cms)4bit (age)1bit (biased)2bit (lock)说明无锁状态 (Neutral)未用HashCode未用分代年龄001is_neutral()为 true偏向锁 (Biased)ThreadID (54bit)Epoch (2bit)未用分代年龄101fast_enter优先处理轻量级锁 (Lightweight)指向栈中 BasicLock 的指针 (62bit)00has_locker()为 true重量级锁 (Heavyweight)指向 ObjectMonitor 的指针 (62bit)10最终膨胀落点总结JVM 在做出“膨胀为重量级锁”这个沉重决定之前会利用轻量级锁做最后的努力。其核心判断准则是只要对象目前没有被其他线程实质性占用无锁就用 CAS 赌一把将其转化为轻量级锁即使被占用了如果发现占用者是自己就通过简化的栈记录NULL displaced header来实现快速重入。只有当这两项尝试全部宣告失败即存在多线程真正的交替竞争或同时抢占JVM 才会死心塌地地调用inflate走向重量级锁。

更多文章