轻量级锁加锁时,mark word被cas替换为指向线程栈中lock record的指针;解锁时cas恢复原值,失败则升级为重量级锁;锁升级不可逆,且对象处于偏向锁、重量级锁或mark word不满足无锁条件时直接跳过轻量级锁。

轻量级锁加锁时,Mark Word怎么被替换成指向Lock Record的指针
Java对象头里的Mark Word在轻量级锁加锁时不会直接写入锁标志位,而是先在当前线程栈帧中分配一个Lock Record结构,再用CAS把Mark Word从“无锁状态”(比如对象哈希码或分代年龄)替换成指向这个Lock Record的指针。
关键点在于:这个替换必须是原子的,且仅当Mark Word仍处于期望的无锁值时才成功。一旦失败,说明有竞争,会升级为重量级锁。
-
Lock Record不是堆上对象,它在当前线程的栈上分配,生命周期与同步块绑定 - CAS操作的目标是对象头的
Mark Word,旧值必须是未锁定状态(如低3位为01),新值是栈中Lock Record的地址(低2位强制设为00,表示轻量级锁) - 如果多个线程同时尝试CAS,只有一个能成功;其余线程发现失败后,会进入自旋等待,而非立即阻塞
为什么自旋失败后要升级成重量级锁,而不是继续尝试CAS
自旋本质是空转等待,靠CPU时间换上下文切换开销。但当竞争持续存在(比如临界区执行久、线程数多),自旋反而浪费资源,且无法保证公平性——可能某个线程永远抢不到锁。
JVM在检测到一定次数自旋失败(默认10次,由-XX:PreBlockSpin控制,JDK 6+已废弃该参数,实际逻辑固化在代码中)或当前线程被调度出CPU后,就会触发锁膨胀:把Mark Word更新为指向ObjectMonitor的指针,并将等待线程挂起。
立即学习“Java免费学习笔记(深入)”;
- 重量级锁的
Mark Word低3位变为10,指向堆上的ObjectMonitor结构 - 此时所有后续加锁请求都直接走
ObjectMonitor::enter,不再尝试轻量级路径 - 注意:锁升级不可逆,即使之后竞争消失,也不会降级回轻量级锁
Lock Record里存了什么,解锁时怎么恢复Mark Word
每个Lock Record至少包含两个字段:obj(保存原Mark Word值)和lock(指向对象头的指针)。解锁时,JVM会用CAS把obj字段的值写回Mark Word,前提是当前Mark Word仍等于指向该Lock Record的指针。
这一步确保了解锁的安全性:如果期间发生锁膨胀,Mark Word早已变成指向ObjectMonitor的指针,CAS失败,当前线程就去调用ObjectMonitor::exit完成重量级解锁。
- 解锁的CAS操作同样可能失败,失败即说明锁已膨胀,需交由
ObjectMonitor处理 -
Lock Record本身在栈上,方法退出时自然销毁,无需GC介入 - 若同步块内发生异常,JVM仍会保证
Lock Record被正确清理并尝试恢复Mark Word
哪些情况会让轻量级锁完全失效,直接跳过尝试
不是所有对象都能走轻量级锁路径。JVM会在对象被标记为偏向锁状态、或已处于重量级锁状态时,绕过轻量级锁逻辑。
更隐蔽的是:只要对象头的Mark Word当前值不满足“无锁且可被CAS替换”的条件(例如已被设置为偏向锁ID、或已被其他线程成功轻量加锁、或已膨胀),当前线程就会直接进入重量级锁流程。
- 对象刚创建时默认可偏向,若未禁用偏向锁(
-XX:-UseBiasedLocking),首次加锁大概率走偏向路径,而非轻量级 - 若对象已被GC移动过(比如经过一次Minor GC),其
Mark Word可能包含hashcode,而hashcode与锁共用同一段存储空间,此时轻量级锁无法安全写入指针 - 32位JVM下
Mark Word只有4字节,存放指针+锁标志位空间紧张,某些版本会更早放弃轻量级锁
真正难调试的不是加锁过程,而是锁状态在栈帧、对象头、ObjectMonitor三者之间的隐式流转。一旦出现锁升级时机异常或自旋行为不符合预期,得结合-XX:+PrintJNIGCStalls、-XX:+TraceClassLoading甚至hsdis反汇编才能定位——毕竟这些操作全在HotSpot C++层完成,Java层完全不可见。








