揭秘Java synchronize:轻量级锁升级与偏向锁
偏向锁
JDK 15:从 JDK 15 开始(JEP 374),偏向锁(Biased Locking)被默认禁用,但仍可通过
-XX:+UseBiasedLocking
显式启用。JDK 21:在 JDK 21 中(JEP 429),偏向锁被完全移除,相关代码和标志(如
UseBiasedLocking
)被删除,从此无法启用。现代 JVM:在 JDK 21 及之后的版本中,偏向锁的逻辑确实已完全不存在。但在 JDK 15~20 中,虽然默认禁用,代码仍保留且可通过参数激活。
代码位置:
ObjectSynchronizer
(HotSpot 的同步原语实现)- 解释器(处理字节码的同步操作)
- JIT 编译器(生成优化后的同步代码)
补充细节:
- 移除原因:偏向锁在现代多核处理器场景下(尤其是短生命周期对象)带来的性能收益有限,且维护复杂度高。
- 替代优化:JVM 转而依赖更高效的轻量级锁(thin lock)和自适应自旋(adaptive spinning)。
轻量级锁
lightweightSynchronizer
中体现的轻量级锁的加锁逻辑以及其升级为重量级锁的过程。
整个过程可以分为三个主要阶段:快速加锁、自旋等待和锁膨胀(升级)。
1. 轻量级锁的获取逻辑 (快速路径)
当一个线程尝试进入一个 synchronized
代码块时,JVM 会调用 LightweightSynchronizer::enter
。其核心是尝试以一种非常高效、无阻塞的方式获取锁。
这个快速路径由 LightweightSynchronizer::fast_lock_try_enter
函数实现:
// ... existing code ...
inline bool LightweightSynchronizer::fast_lock_try_enter(oop obj, LockStack& lock_stack, JavaThread* current) {markWord mark = obj->mark();while (mark.is_unlocked()) {ensure_lock_stack_space(current);assert(!lock_stack.is_full(), "must have made room on the lock stack");assert(!lock_stack.contains(obj), "thread must not already hold the lock");// Try to swing into 'fast-locked' state.markWord locked_mark = mark.set_fast_locked();markWord old_mark = mark;mark = obj->cas_set_mark(locked_mark, old_mark);if (old_mark == mark) {// Successfully fast-locked, push object to lock-stack and return.lock_stack.push(obj);return true;}}return false;
}
// ... existing code ...
逻辑分析:
- 检查状态: 函数首先读取对象头(
markWord
),并检查它是否处于“无锁”(is_unlocked()
)状态。 - 确保空间: 调用
ensure_lock_stack_space
确保当前线程的锁记录栈(LockStack
)有足够空间。LockStack
用于记录线程持有的轻量级锁,避免了重量级锁的开销。 - CAS尝试: 这是最关键的一步。它使用一个原子的“比较并交换”(CAS)操作 (
cas_set_mark
),尝试将对象头的状态从“无锁”更新为“轻量级锁定”(fast-locked
)。 - 成功获取: 如果 CAS 操作成功,意味着没有其他线程同时在竞争这个锁。这时,函数会将该对象的指针压入当前线程的
LockStack
中,并返回true
,表示成功获取了轻量级锁。整个过程非常快,因为它不涉及操作系统层面的互斥量。
2. 轻量级锁的自旋等待
如果 fast_lock_try_enter
失败(即 CAS 操作失败),说明在它尝试加锁的瞬间,有另一个线程已经持有了这个锁。此时,JVM 不会立即放弃,而是会进入一个短暂的“自旋”等待阶段,由 fast_lock_spin_enter
函数实现。
// ... existing code ...
bool LightweightSynchronizer::fast_lock_spin_enter(oop obj, LockStack& lock_stack, JavaThread* current, bool observed_deflation) {assert(UseObjectMonitorTable, "must be");// Will spin with exponential backoff with an accumulative O(2^spin_limit) spins.const int log_spin_limit = os::is_MP() ? LightweightFastLockingSpins : 1;
// ... existing code ...for (int i = 0; should_spin() && !should_process && i < log_spin_limit; i++) {// Spin with exponential backoff.
// ... existing code ...for (int inner = 1; inner < inner_spin_count; inner++) {SpinPause();}if (fast_lock_try_enter(obj, lock_stack, current)) return true;}return false;
}
// ... existing code ...
逻辑分析:
- 自旋条件: 函数进入一个循环,循环次数由
LightweightFastLockingSpins
控制。 - 指数退避: 它采用指数退避策略(
1 << i
),即每次自旋的等待时间会逐渐变长,避免过多地消耗 CPU。 - 循环尝试: 在每次自旋等待后,它会再次调用
fast_lock_try_enter
尝试获取锁。 - 自旋的目的: 这种设计的假设是,持有锁的线程会很快释放它。通过自旋,可以避免立即进入重量级锁的挂起和唤醒流程,从而在低度竞争下保持高性能。
3. 锁升级(膨胀)逻辑
如果自旋结束后,线程仍然没有获取到锁,或者遇到了其他需要膨胀的条件,轻量级锁就会升级为重量级锁。
触发条件:
- 自旋失败:
fast_lock_spin_enter
返回false
。 - 递归锁问题: 一个线程已经持有了锁A,然后尝试获取锁B,再在B的同步块内递归地获取锁A。轻量级锁的
LockStack
不支持这种交错的锁记录(如[A, B, A]
),此时必须将锁A膨胀。 LockStack
溢出: 线程的LockStack
容量是有限的。如果递归调用过深,导致LockStack
满了,JVM 会强制膨胀栈上的锁来释放空间。这由ensure_lock_stack_space
函数处理。
膨胀过程: 锁膨胀的核心逻辑最终会走到 LightweightSynchronizer::inflate_and_enter
LightweightSynchronizer::inflate_and_enter
这个函数是轻量级锁升级为重量级锁过程中的核心枢纽。当一个线程无法通过快速路径(fast_lock_try_enter
)或自旋(fast_lock_spin_enter
)获取锁时,就会调用此函数来完成锁的膨胀(inflation)并尝试进入(enter)这个新膨胀的重量级锁。
ObjectMonitor* LightweightSynchronizer::inflate_and_enter(oop object, // 1. 目标锁对象BasicLock* lock, // 2. 线程栈上的锁记录ObjectSynchronizer::InflateCause cause, // 3. 膨胀原因JavaThread* locking_thread, // 4. 真正想要获取锁的线程JavaThread* current // 5. 当前执行此代码的线程
)
object
: 需要被加锁的对象。lock
: 当前线程栈上的BasicLock
记录,用于缓存ObjectMonitor
指针以提高后续访问速度。cause
: 触发这次膨胀的原因,例如是普通的monitorenter
争用,还是因为LockStack
溢出等。locking_thread
: 真正需要获取锁的线程。current
: 实际执行这段 C++ 代码的线程。在大多数情况下,locking_thread
和current
是同一个线程。但在某些特殊场景下(如 deoptimization),一个线程可能会代表另一个被挂起的线程来执行锁膨胀操作。
这个函数的逻辑非常复杂,因为它需要处理多种并发状态和竞争条件。我们分步来看:
1. 准备工作和缓存读取
// ... existing code ...NoSafepointVerifier nsv;// Try to get the monitor from the thread-local cache.// There's no need to use the cache if we are locking// on behalf of another thread.if (current == locking_thread) {monitor = read_caches(current, lock, object);}// Get or create the monitorif (monitor == nullptr) {// Lightweight monitors require that hash codes are installed firstObjectSynchronizer::FastHashCode(locking_thread, object);monitor = get_or_insert_monitor(object, current, cause);}
// ... existing code ...
- 读取缓存: 首先,如果
current
和locking_thread
是同一个线程,它会尝试从线程本地的BasicLock
缓存中读取ObjectMonitor
。如果之前已经膨胀过这个锁,这里可以直接拿到ObjectMonitor
指针,避免重复查找。 - 获取或创建 Monitor: 如果缓存未命中,则:
- 调用
ObjectSynchronizer::FastHashCode
确保对象有一个哈希码。这是因为UseObjectMonitorTable
模式下,全局表使用对象的哈希码作为 key 来定位ObjectMonitor
。 - 调用
get_or_insert_monitor
,它会原子地从ObjectMonitorTable
中获取或创建并插入一个新的ObjectMonitor
。
2. 快速尝试进入 (Try Enter)
// ... existing code ...if (monitor->try_enter(locking_thread)) {return monitor;}
// ... existing code ...
- 在获取到
ObjectMonitor
后,它会立即调用monitor->try_enter()
。这是一个轻量级的尝试,如果ObjectMonitor
当前未被任何线程持有,try_enter
会通过一个 CAS 操作直接将_owner
设置为locking_thread
并成功返回。如果成功,函数直接返回monitor
,膨胀和加锁一次性完成,效率很高。
3. 处理锁降级(Deflation)冲突
// ... existing code ...// Holds is_being_async_deflated() stable throughout this function.ObjectMonitorContentionMark contention_mark(monitor);/// First handle the case where the monitor from the table is deflatedif (monitor->is_being_async_deflated()) {// ... (代码省略) ...// Retryreturn nullptr;}
// ... existing code ...
- 如果
try_enter
失败,说明锁已经被其他线程持有,或者正在发生一些复杂的状态变化。 ObjectMonitorContentionMark
是一个 RAII 对象,它在构造时会增加monitor
的争用计数(_contentions
)。这个计数器的一个重要作用是防止锁降级。当_contentions
大于 0 时,MonitorDeflationThread
就不会对这个monitor
进行降级操作。- 接着,代码检查
monitor->is_being_async_deflated()
。这是一种罕见但必须处理的竞态条件:我们刚拿到一个monitor
,但服务线程恰好正在对它进行降级(deflate)。 - 如果发生这种情况,当前线程不能继续操作,必须放弃。它会清理缓存,通过
os::naked_yield()
让出 CPU,然后返回nullptr
。外层的调用者(LightweightSynchronizer::enter
)看到nullptr
后会重新循环,再次尝试整个加锁过程。
4. 同步对象头状态 (Mark Word Synchronization)
这是函数最复杂的部分。此时,我们手上有了一个有效的 ObjectMonitor
,但对象头(markWord
)的状态可能还是 fast-locked
或者 unlocked
。我们需要原子地将对象头的状态更新为 has_monitor
,并正确设置 ObjectMonitor
的所有者。
// ... existing code ...for (;;) {const markWord mark = object->mark_acquire();// ...// CASE: inflatedif (mark.has_monitor()) {// ...break; // Success}// CASE: fast-lockedif (mark.is_fast_locked()) {markWord old_mark = object->cas_set_mark(mark.set_has_monitor(), mark);// ...break; // Success}// CASE: neutral (unlocked)assert(mark.is_neutral(), "invariant: header=" INTPTR_FORMAT, mark.value());markWord old_mark = object->cas_set_mark(mark.set_has_monitor(), mark);// ...// Transitioned from unlocked to monitor means locking_thread owns the lock.monitor->set_owner_from_anonymous(locking_thread);return monitor;}
// ... existing code ...
- 函数进入一个
for(;;)
循环,通过 CAS 操作来同步状态,处理并发修改。 - 如果对象头已经是
has_monitor
: 说明别的线程已经完成了膨胀。这时需要检查monitor
的所有者。如果所有者是匿名的(has_anonymous_owner()
)并且当前线程的LockStack
上有这个对象,说明是当前线程之前轻量级锁定了它,现在需要将monitor
的所有权正式交给自己。 - 如果对象头是
fast-locked
: 这意味着锁仍处于轻量级锁定状态。代码会尝试用 CAS 将其状态改为has_monitor
。成功后,同样需要根据LockStack
的信息来确定并设置monitor
的所有者。 - 如果对象头是
unlocked
: 这通常发生在无锁对象上直接调用Object.wait()
等需要重量级锁的操作。代码会尝试用 CAS 将其状态改为has_monitor
。如果成功,因为是从无锁状态直接膨胀并加锁,所以locking_thread
直接成为所有者。
5. 进入重量级锁的慢速路径
如果经过上述所有步骤,锁仍然被其他线程持有,那么就必须进入重量级锁的完整争用流程。
// ... existing code ...if (current == locking_thread) {// One round of spinningif (monitor->spin_enter(locking_thread)) {return monitor;}// ...}// ...if (current == locking_thread) {monitor->enter_with_contention_mark(locking_thread, contention_mark);} else {monitor->enter_for_with_contention_mark(locking_thread, contention_mark);}return monitor;
}
- 最后一次自旋: 调用
monitor->spin_enter()
做最后一轮自旋尝试。 - 进入等待队列: 如果自旋仍然失败,就调用
monitor->enter_with_contention_mark()
。这个函数会把当前线程加入到ObjectMonitor
的_EntryList
等待队列中,然后线程会被挂起(park),直到被持有锁的线程在释放锁时唤醒。
inflate_and_enter
是一个高度优化的函数,它体现了 JVM 在处理锁竞争时的复杂性和精妙性:
- 多层缓存: 优先使用线程本地缓存。
- 原子操作: 大量使用 CAS 来处理并发和状态转换,避免了更重的锁。
- 状态同步: 精确地处理
ObjectMonitor
、ObjectMonitorTable
、对象头markWord
和线程LockStack
之间复杂的状态同步。 - 冲突处理: 优雅地处理了与锁降级(deflation)的并发冲突。
- 分层进入: 从快速的
try_enter
,到自旋的spin_enter
,再到最终的enter_with_contention_mark
,层层递进,尽可能地降低开销。
总结
轻量级锁的逻辑是一个精巧的性能优化分层:
- 第一层 (Fast Path): 尝试一次无竞争的 CAS 操作,这是最理想、最快的情况。
- 第二层 (Spinning): 如果有轻微竞争,通过短暂的自旋等待,期望锁能快速释放,避免线程挂起的开销。
- 第三层 (Inflation): 如果竞争激烈或遇到资源限制,则放弃轻量级锁,升级为功能完备但开销更大的重量级锁(
ObjectMonitor
),以处理复杂的同步场景。
A占有轻量级锁,B来抢锁会发生什么?
总结一下线程 B 的行为:
- 短暂自旋: 当线程 A 持有轻量级锁时,线程 B 会进行有限次数的自旋,并在这期间反复尝试 CAS 获取锁。
- 停止自旋: 如果在自旋次数耗尽后,线程 A 仍然没有释放锁,线程 B 就会停止自旋。
- 主动膨胀: 停止自旋后,线程 B 会主动调用
inflate_and_enter
,将锁膨胀为重量级锁。 - 线程 B 会创建一个新的 ObjectMonitor,并将其原子地放入全局的 ObjectMonitorTable 中。这个新创建的 ObjectMonitor 的所有者是匿名的 (has_anonymous_owner() 会返回 true)。
- 然后,线程 B 通过 CAS 操作,将对象头(mark word)从“轻量级锁定”状态修改为指向这个新 ObjectMonitor 的“已膨胀”状态。
- 完成这些后,线程 B 会在 monitor->enter() 中被挂起,等待被唤醒。
线程 A 的角色(原所有者和释放者):
- 当线程 A 执行完同步代码,调用 exit 函数时,它首先会尝试进行轻量级解锁。
- 它尝试通过 CAS 将对象头恢复为“无锁”状态。但是,这个 CAS 会失败,因为对象头已经被线程 B 修改为“已膨胀”状态了。
- while (mark.is_fast_locked()) 循环会因为 mark 不再是 fast_locked 而退出。
- 代码执行到 assert(mark.has_monitor(), "must be");,断言通过。
- 线程 A 从全局表中获取到由 B 创建的那个 ObjectMonitor。
- 接下来是最关键的一步:线程 A 调用 monitor->has_anonymous_owner(),发现所有者是匿名的。
- 此时,线程 A 就明白了:在我持有锁的期间,有人把锁膨胀了。于是,它执行 monitor->set_owner_from_anonymous(current);,这里的 current 就是线程 A。这行代码正式将 ObjectMonitor 的所有者设置为线程 A。
- 紧接着,线程 A 调用 monitor->exit(current),执行重量级锁的退出逻辑,这会清空 _owner 字段并唤醒在等待队列中的线程 B。
设置匿名Owner
LightweightSynchronizer::get_or_insert_monitor_from_table 创建后设置匿名
ObjectMonitor* LightweightSynchronizer::get_or_insert_monitor_from_table(oop object, JavaThread* current, bool* inserted) {assert(LockingMode == LM_LIGHTWEIGHT, "must be");ObjectMonitor* monitor = get_monitor_from_table(current, object);if (monitor != nullptr) {*inserted = false;return monitor;}ObjectMonitor* alloced_monitor = new ObjectMonitor(object);alloced_monitor->set_anonymous_owner();// Try insert monitormonitor = add_monitor(current, alloced_monitor, object);*inserted = alloced_monitor == monitor;if (!*inserted) {delete alloced_monitor;}return monitor;
}
LightweightSynchronizer::exit
函数逻辑分析
void LightweightSynchronizer::exit(oop object, BasicLock* lock, JavaThread* current) {assert(LockingMode == LM_LIGHTWEIGHT, "must be");assert(current == Thread::current(), "must be");markWord mark = object->mark();assert(!mark.is_unlocked(), "must be");LockStack& lock_stack = current->lock_stack();// ... (处理递归锁和非结构化解锁) ...// 1. 尝试轻量级锁解锁while (mark.is_fast_locked()) {markWord unlocked_mark = mark.set_unlocked();markWord old_mark = mark;mark = object->cas_set_mark(unlocked_mark, old_mark);if (old_mark == mark) {// CAS 成功,从 lock_stack 移除并返回size_t recursion = lock_stack.remove(object) - 1;assert(recursion == 0, "Should not have unlocked here");return;}}// 2. 如果不是轻量级锁,则一定是重量级锁assert(mark.has_monitor(), "must be");// The monitor existsObjectMonitor* monitor;// ... (从缓存或全局表获取 monitor) ...if (monitor->has_anonymous_owner()) {// ... (处理匿名所有者的情况,将所有权正式交给自己) ...}// 3. 执行重量级锁的退出逻辑monitor->exit(current);
}
核心逻辑分解:
- 读取对象头: 函数首先读取对象头
mark
。 - 判断锁状态:
情况一:轻量级锁 (
mark.is_fast_locked()
为 true)- 这是最常见的情况,表示没有发生锁竞争和膨胀。
- 函数会进入一个
while
循环,尝试通过 CAS 操作将对象头从“轻量级锁定”状态恢复为“无锁”状态 (set_unlocked()
)。 - 如果 CAS 成功,说明解锁完成。函数会从当前线程的
LockStack
中移除该对象的记录,然后直接返回。 - 如果 CAS 失败,意味着在解锁的瞬间,有其他线程正在膨胀这个锁。
mark
会被更新为最新的值(此时应该是has_monitor
状态),循环条件mark.is_fast_locked()
不再满足,程序会跳出循环,进入下面的重量级锁解锁逻辑。
情况二:重量级锁 (
mark.has_monitor()
为 true)- 这说明在当前线程持有锁的期间,有其他线程(比如线程 B)前来竞争,导致锁被膨胀了。
- 代码会从线程本地缓存或全局的
ObjectMonitorTable
中获取到与该对象关联的ObjectMonitor
。 - 最后,调用
monitor->exit(current)
。这个函数会执行重量级锁的退出逻辑,包括将_owner
置空,并从等待队列(_EntryList
)中唤醒一个等待的线程(比如线程 B)。
这个函数完美地体现了 synchronized
的自适应性:它首先尝试廉价的轻量级解锁,如果发现锁状态已经改变(被膨胀),则无缝切换到重量级解锁路径。
存储变化是怎么样的?
当锁从轻量级膨胀为重量级时,涉及到的存储区域和数据结构会发生一系列精确而复杂的变化。我们以线程 A 持有轻量级锁,线程 B 竞争导致膨胀的时刻为界,看看前后发生了什么。
膨胀前 (A 持有轻量级锁)
对象头 (Mark Word):
- 对象
obj
的 Mark Word 不再是普通的哈希码或年龄,而是一个指针,指向线程 A 的线程栈上的BasicLock
记录。 - Mark Word 的最低两位被设置为
00
,表示“轻量级锁定”状态。
- 对象
线程 A 的栈 (Thread A's Stack):
- 在线程 A 当前的栈帧里,有一个
BasicLock
对象。 - 这个
BasicLock
里的displaced_header
字段保存了对象obj
原始的 Mark Word(即未加锁时的状态)。 - 线程 A 的
LockStack
数据结构中,记录了它持有了obj
这个锁。
- 在线程 A 当前的栈帧里,有一个
堆 (Heap) & 全局表 (Global Table):
- 此时,堆上没有为
obj
分配ObjectMonitor
对象。 - 全局的
ObjectMonitorTable
中也没有关于obj
的条目。
- 此时,堆上没有为
膨胀后 (B 竞争,锁变为重量级,A 仍是所有者)
对象头 (Mark Word):
- 这是变化最核心的地方。对象
obj
的 Mark Word 被线程 B 通过 CAS 操作修改为一个指针,指向一个在堆上新创建的ObjectMonitor
对象。 - Mark Word 的最低两位被设置为
10
,表示“已膨胀/重量级锁定”状态。
- 这是变化最核心的地方。对象
堆 (Heap):
- 线程 B 在堆上为
obj
创建了一个全新的ObjectMonitor
对象。 - 这个
ObjectMonitor
的_owner
字段在刚被创建时是匿名的。 ObjectMonitor
的_EntryList
(等待队列)中,现在包含了线程 B。ObjectMonitor
的_header
字段会保存对象obj
原始的 Mark Word(这个信息是在膨胀过程中从线程 A 的BasicLock
中恢复出来的)。
- 线程 B 在堆上为
全局
ObjectMonitorTable
:- 一个新条目被添加到这个全局哈希表中,建立了从
obj
到它在堆上的ObjectMonitor
的映射。
- 一个新条目被添加到这个全局哈希表中,建立了从
线程 A 的栈 (Thread A's Stack):
- 它的
BasicLock
记录中的displaced_header
已经完成了历史使命,它的信息已被用于初始化ObjectMonitor
。 - 当线程 A 最终退出同步块时,它会发现锁已被膨胀,并将
obj
从自己的LockStack
中移除。
- 它的
这个过程将一个与线程栈紧密耦合的锁,转变成了一个独立存在于堆上、由全局表管理的、功能完备的重量级锁。
一个 CAS 怎么判断轻量级锁是否失效?会不会有冲突?
这是一个绝妙的问题,它触及了无锁编程和并发控制的核心。
CAS(Compare-And-Swap)是一个原子操作,它有三个参数:CAS(内存地址V, 期望的旧值A, 要写入的新值B)
。只有当内存地址 V
的当前值等于期望的旧值 A
时,它才会把 V
的值更新为新值 B
,并返回成功。否则,它什么也不做,并返回失败。
在轻量级锁解锁的场景下(LightweightSynchronizer::exit
),线程 A 的操作是这样的:
- 内存地址 V: 对象
obj
的 Mark Word 的地址。 - 期望的旧值 A: 指向线程 A 自己栈上那个
BasicLock
记录的指针。这是线程 A 在加锁时写入 Mark Word 的值。 - 要写入的新值 B: 存在
BasicLock
里的那个原始的、未加锁的 Mark Word。
判断过程就蕴含在 CAS 的原子性中:
- 如果成功: 这意味着从 A 加锁到 A 解锁的这段时间里,
obj
的 Mark Word 从未被改变过,它仍然是指向 A 的BasicLock
的指针。这说明没有其他线程来竞争并膨胀这个锁。CAS 成功地将 Mark Word 恢复原状,解锁完成。 - 如果失败: 这意味着
obj
的 Mark Word 的当前值已经不等于“指向 A 的BasicLock
的指针”了。为什么会变?唯一的原因就是有另一个线程(比如 B)已经将它修改为了“指向堆上ObjectMonitor
的指针”。CAS 的失败本身就是一个明确的信号:“锁已经被膨胀了,你不能再用轻量级的方式解锁了”。
会不会有冲突?如何解决?
会,而且这正是这套机制设计的核心所在。 冲突主要发生在线程 A 尝试轻量级解锁和线程 B 尝试膨胀锁的时刻,它们都在对同一个 Mark Word 进行 CAS 操作。
冲突的解决机制:
由于 CAS 是原子的,这两个操作不可能同时成功,必然有先后之分。
情况一:线程 A 的解锁 CAS 先成功
- Mark Word 被恢复为无锁状态。
- 线程 B 随后的膨胀 CAS 就会失败,因为它期望的旧值是“指向 A 的
BasicLock
的指针”,但现在已经是“无锁”状态了。 - 线程 B 会重新读取 Mark Word,发现锁已经可用,于是它会重新走一遍加锁流程,这次它可能会成功获取到轻量级锁。
情况二:线程 B 的膨胀 CAS 先成功
- Mark Word 被更新为“指向
ObjectMonitor
的指针”。 - 线程 A 随后的解锁 CAS 就会失败,因为它期望的旧值是“指向自己的
BasicLock
的指针”,但现在已经被 B 改掉了。 - 如上所述,CAS 的失败告诉线程 A,锁已被膨胀。于是,线程 A 会放弃轻量级解锁,转而执行重量级解锁的逻辑:获取
ObjectMonitor
,然后调用monitor->exit()
来释放锁并唤醒等待的线程 B。
- Mark Word 被更新为“指向
总结: CAS 在这里扮演了一个完美的原子仲裁者。它利用“比较-交换”的特性,不仅保证了操作的原子性,还巧妙地利用了操作的失败来传递状态变化的信息。这种“冲突”不是问题,而是被设计用来驱动锁状态从轻量级向重量级正确、安全地迁移的关键机制。