当前位置: 首页 > news >正文

揭秘Java synchronize:轻量级锁升级与偏向锁

 偏向锁

  1. ​JDK 15​​:从 JDK 15 开始(JEP 374),偏向锁(Biased Locking)被​​默认禁用​​,但仍可通过 -XX:+UseBiasedLocking 显式启用。

  2. ​JDK 21​​:在 JDK 21 中(JEP 429),偏向锁被​​完全移除​​,相关代码和标志(如 UseBiasedLocking)被删除,从此无法启用。

  3. ​现代 JVM​​:在 JDK 21 及之后的版本中,偏向锁的逻辑确实已完全不存在。但在 JDK 15~20 中,虽然默认禁用,代码仍保留且可通过参数激活。

  4. ​代码位置​​:

    • 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 ...

逻辑分析:

  1. 检查状态: 函数首先读取对象头(markWord),并检查它是否处于“无锁”(is_unlocked())状态。
  2. 确保空间: 调用 ensure_lock_stack_space 确保当前线程的锁记录栈(LockStack)有足够空间。LockStack 用于记录线程持有的轻量级锁,避免了重量级锁的开销。
  3. CAS尝试: 这是最关键的一步。它使用一个原子的“比较并交换”(CAS)操作 (cas_set_mark),尝试将对象头的状态从“无锁”更新为“轻量级锁定”(fast-locked)。
  4. 成功获取: 如果 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 ...

逻辑分析:

  1. 自旋条件: 函数进入一个循环,循环次数由 LightweightFastLockingSpins 控制。
  2. 指数退避: 它采用指数退避策略(1 << i),即每次自旋的等待时间会逐渐变长,避免过多地消耗 CPU。
  3. 循环尝试: 在每次自旋等待后,它会再次调用 fast_lock_try_enter 尝试获取锁。
  4. 自旋的目的: 这种设计的假设是,持有锁的线程会很快释放它。通过自旋,可以避免立即进入重量级锁的挂起和唤醒流程,从而在低度竞争下保持高性能。

3. 锁升级(膨胀)逻辑

如果自旋结束后,线程仍然没有获取到锁,或者遇到了其他需要膨胀的条件,轻量级锁就会升级为重量级锁。

触发条件:

  1. 自旋失败fast_lock_spin_enter 返回 false
  2. 递归锁问题: 一个线程已经持有了锁A,然后尝试获取锁B,再在B的同步块内递归地获取锁A。轻量级锁的 LockStack 不支持这种交错的锁记录(如 [A, B, A]),此时必须将锁A膨胀。
  3. 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. 当前执行此代码的线程
)
  1. object: 需要被加锁的对象。
  2. lock: 当前线程栈上的 BasicLock 记录,用于缓存 ObjectMonitor 指针以提高后续访问速度。
  3. cause: 触发这次膨胀的原因,例如是普通的 monitorenter 争用,还是因为 LockStack 溢出等。
  4. locking_thread: 真正需要获取锁的线程。
  5. 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 在处理锁竞争时的复杂性和精妙性:

  1. 多层缓存: 优先使用线程本地缓存。
  2. 原子操作: 大量使用 CAS 来处理并发和状态转换,避免了更重的锁。
  3. 状态同步: 精确地处理 ObjectMonitorObjectMonitorTable、对象头 markWord 和线程 LockStack 之间复杂的状态同步。
  4. 冲突处理: 优雅地处理了与锁降级(deflation)的并发冲突。
  5. 分层进入: 从快速的 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);
}

核心逻辑分解:

  1. 读取对象头: 函数首先读取对象头 mark
  2. 判断锁状态:
    • 情况一:轻量级锁 (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 持有轻量级锁)
  1. 对象头 (Mark Word):

    • 对象 obj 的 Mark Word 不再是普通的哈希码或年龄,而是一个指针,指向线程 A 的线程栈上的 BasicLock 记录。
    • Mark Word 的最低两位被设置为 00,表示“轻量级锁定”状态。
  2. 线程 A 的栈 (Thread A's Stack):

    • 在线程 A 当前的栈帧里,有一个 BasicLock 对象。
    • 这个 BasicLock 里的 displaced_header 字段保存了对象 obj 原始的 Mark Word(即未加锁时的状态)。
    • 线程 A 的 LockStack 数据结构中,记录了它持有了 obj 这个锁。
  3. 堆 (Heap) & 全局表 (Global Table):

    • 此时,堆上没有为 obj 分配 ObjectMonitor 对象。
    • 全局的 ObjectMonitorTable 中也没有关于 obj 的条目。
膨胀后 (B 竞争,锁变为重量级,A 仍是所有者)
  1. 对象头 (Mark Word):

    • 这是变化最核心的地方。对象 obj 的 Mark Word 被线程 B 通过 CAS 操作修改为一个指针,指向一个在堆上新创建的 ObjectMonitor 对象。
    • Mark Word 的最低两位被设置为 10,表示“已膨胀/重量级锁定”状态。
  2. 堆 (Heap):

    • 线程 B 在堆上为 obj 创建了一个全新的 ObjectMonitor 对象
    • 这个 ObjectMonitor 的 _owner 字段在刚被创建时是匿名的
    • ObjectMonitor 的 _EntryList(等待队列)中,现在包含了线程 B。
    • ObjectMonitor 的 _header 字段会保存对象 obj 原始的 Mark Word(这个信息是在膨胀过程中从线程 A 的 BasicLock 中恢复出来的)。
  3. 全局 ObjectMonitorTable:

    • 一个新条目被添加到这个全局哈希表中,建立了从 obj 到它在堆上的 ObjectMonitor 的映射。
  4. 线程 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。

总结: CAS 在这里扮演了一个完美的原子仲裁者。它利用“比较-交换”的特性,不仅保证了操作的原子性,还巧妙地利用了操作的失败来传递状态变化的信息。这种“冲突”不是问题,而是被设计用来驱动锁状态从轻量级向重量级正确、安全地迁移的关键机制。

http://www.dtcms.com/a/321862.html

相关文章:

  • Java-注解
  • 重新 mybatis plus 的 撒着OrUpdate 方法,实现根据自定义字段插入或者修改
  • P1044 [NOIP 2003 普及组] 栈
  • B4263 [GESP202503 四级] 荒地开垦 题解
  • 【工作笔记】Docker Desktop一直转圈加载不出来然后报错
  • 提升LLM服务效率的秘密武器——vLLM!
  • Docker 安装 Redis
  • 机柜中不同类型板卡的操作系统配置情况一览
  • 解决苍穹外卖项目中 MyBatis - Plus 版本冲突问题
  • 【Linux运维】深入理解Cookie与Session机制:安全性与性能的平衡艺术
  • SAP接口日志查询
  • 多级缓存架构:新品咖啡上线引发的数据库压力风暴与高并发实战化解方案
  • 数据返回后需要刷新才会展示的解决方法
  • Vue3 组合式API
  • 飞算JavaAI深度解析:专为Java生态而生的智能引擎
  • 快速了解svm算法
  • Java 执行 SFTP 文件上传和下载
  • ​​《深入浅出K-means算法:从原理到实战全解析》​预告(提纲)
  • 【Spring Boot 快速入门】八、登录认证(一)基础登录与认证校验
  • 阿里巴巴高级Java工程师面试算法真题解析:LRU Cache实现
  • 详解 RT-Thread 串口一配置、设备查找与打印功能(rt_kprintf)的绑定机制
  • 完整设计 之 运行时九宫格 (太乙九宫 播放器)
  • AI 记忆管理系统:工程实现设计方案
  • 【感知机】感知机(perceptron)学习算法知识点汇总
  • 代码随想录算法训练营第三十八天、三十九天|动态规划part11、12
  • 【LLM开发学习】
  • 小程序实现二维码图片Buffer下载
  • C#结合HALCON去除ROI选中效果的实现方法
  • django uwsgi启动报错failed to get the Python codec of the filesystem encoding
  • 如何永久删除三星手机中的照片?