JDK21中多线程竞争导致ObjectMonitor的CAS状态转移逻辑剖析


前言

本文旨在记录近期研读Java源码的学习心得与疑难问题。由于个人理解水平有限,文中内容难免存在疏漏,恳请读者不吝指正。

多线程竞争导致ObjectMonitor的CAS状态转移

LightweightSynchronizer 膨胀过程中,多线程竞争导致 ObjectMonitor CAS 状态转移的具体实现逻辑分析。

1. 锁膨胀并发竞争的核心逻辑与状态机演进

在 JDK 21 的 -XX:+UseLightweightLocking 机制下,多个线程可能同时发现同一个 Java 对象需要进行锁膨胀。常见的触发场景包括:

  1. LockStack 溢出:某个线程嵌套/递归加锁深度达到 8 次,再次加锁触发 Bounds Check 跳转慢速路径。
  2. 多线程并发抢占:线程 A 持有轻量锁,线程 B 尝试获取锁失败,进入慢速路径主动发起锁膨胀。
  3. 全局/显式操作:线程在无锁对象上调用 Object.wait()System.identityHashCode() 等必须依附 ObjectMonitor 的 API。

由于锁膨胀本身是一个包含堆外内存分配(ObjectMonitor)、属性初始化、Mark Word 原子更新等多步骤的过程,多线程同时对同一个对象进行 inflate 时,HotSpot 必须确保只有一个线程能够成功挂载其分配的 ObjectMonitor,而其他并发竞争线程必须能无缝退避并复用已膨胀的 ObjectMonitor

Mark Word 的状态演变路径

[ Unlocked: 001 ] / [ Fast-Locked: 000 ]
         │
         │ 多线程并发调用 ObjectSynchronizer::inflate_impl
         ▼
┌─────────────────────────────────────────────────────────────┐
│ 线程 A / B / C 并发执行 om_allocate() 预分配 ObjectMonitor  │
└──────────────────────────────┬──────────────────────────────┘
                               │
                               ▼
        ┌─────────────────────────────────────────────┐
        │ 执行 object->cas_set_mark(monitor_mark, mark) │
        └──────────────┬───────────────┬──────────────┘
                       │               │
            CAS 成功 (Winner)        CAS 失败 (Loser)
                       │               │
                       ▼               ▼
      [ Inflated: 10 (ObjectMonitor*) ]  调用 om_release 回收 Monitor
                       │               重新自旋,直接读取 Winner 的 Monitor
                       ▼               ▼
         ObjectMonitor 挂载完成 / 线程进入 monitor->enter()


2. C++ 源码级逐行与逻辑解构 (synchronizer.cpp)

多线程竞争锁膨胀的核心无锁(Lock-Free)决胜逻辑位于 src/hotspot/share/runtime/synchronizer.cpp 中的 ObjectSynchronizer::inflate_impl(在部分分支代码库中直接为 inflate)。

核心源码实现 (src/hotspot/share/runtime/synchronizer.cpp)

ObjectMonitor* ObjectSynchronizer::inflate_impl(Thread* current, oop object, const InflateCause cause) {
  // 建立自旋重试循环,应对 CAS 失败或并发竞争状态
  for (;;) {
    const markWord mark = object->mark();

    // =========================================================================
    // 场景 1:分支 1 —— 对象已经被其他并发胜出的线程成功膨胀 (Inflated)
    // =========================================================================
    if (mark.has_monitor()) {
      ObjectMonitor* monitor = mark.monitor();
      assert(monitor != nullptr, "monitor should be valid");
      // 竞争失败者或后到者在此直接返回已经挂载成功的 ObjectMonitor 实例
      return monitor;
    }

    // =========================================================================
    // 场景 2:分支 2 —— 对象正在被其他线程执行膨胀 (INFLATING 中中间态)
    // =========================================================================
    if (mark.is_being_inflated()) {
      // 遇到中间过渡态,当前线程退避自旋 (SpinPause),等待胜出线程完成 Mark Word 设置
      SpinPause();
      continue;
    }

    // =========================================================================
    // 场景 3:分支 3 —— 对象处于 Fast-Locked (000) 或 Unlocked (001) 状态 (竞争发起点)
    // =========================================================================
    if (mark.is_fast_locked() || mark.is_unlocked()) {
      // Step 3.1: 预先从当前线程的 Thread-Local Monitor List 或全局 Pool 中分配一个干净的 Monitor
      ObjectMonitor* monitor = om_allocate(current, object);

      // Step 3.2: 根据当前 mark 的状态初始化 ObjectMonitor 的 header 字段
      if (mark.is_fast_locked()) {
        // 若当前处于轻量锁状态,保存 prototype header,防止原始 HashCode/GC 年龄丢失
        monitor->set_header(markWord::prototype());
      } else {
        // 若处于 Unlocked 状态,直接备份当前的 Unlocked markWord
        monitor->set_header(mark);
      }

      // Step 3.3: 构造目标 Inflated 状态的 markWord (将 Monitor 指针编码,低 2 位设为 10)
      markWord monitor_mark = markWord::encode(monitor);

      // Step 3.4: 关键物理 CAS 决胜点:尝试将对象的 Mark Word 从原始 mark 替换为 monitor_mark
      markWord old_mark = object->cas_set_mark(monitor_mark, mark);

      // -----------------------------------------------------------------------
      // 场景 3.4.1:物理 CAS 胜出者 (Winner)
      // -----------------------------------------------------------------------
      if (old_mark == mark) {
        // 成功将 Mark Word 的低位改写为 10,并指向 monitor 实例。
        // 此后,其他所有并发线程通过 object->mark() 均会命中有 monitor 分支 (has_monitor == true)
        return monitor;
      }

      // -----------------------------------------------------------------------
      // 场景 3.4.2:物理 CAS 失败者 (Loser)
      // -----------------------------------------------------------------------
      // 物理 CAS 失败说明:
      // 1. 其他线程抢先完成了膨胀 CAS (old_mark 变成了 Inflated)
      // 2. 锁持有者线程刚好执行了 fast_unlock 修改了 Mark Word
      // 3. 其他线程在该对象上计算了 Identity Hash Code

      // 必须将预先分配但未使用的 ObjectMonitor 重新回收至 Monitor Pool,防止内存泄漏
      om_release(current, monitor, false);

      // 重置后继续自旋,下一次循环将直接读取最新 markWord 并做出正确决策
      continue;
    }

    // ... 其他罕见状态的 Fallback 处理 ...
  }
}


3. 多线程 CAS 竞争的 4 种典型决胜场景分析

场景一:多线程并发膨胀的 Winner 与 Loser 裁决

假设线程 T1(LockStack 溢出)与线程 T2(尝试抢占锁)同时对 Object-X 触发 inflate_impl

线程 T1 (Inflator 1)                     线程 T2 (Inflator 2)
      │                                       │
      ├─► 调用 om_allocate() 拿到 M1         ├─► 调用 om_allocate() 拿到 M2
      │                                       │
      ├─► cas_set_mark(M1, Mark_Old)          ├─► cas_set_mark(M2, Mark_Old)
      │   (CPU 总线/缓存锁裁决)                 │   (CPU 总线/缓存锁裁决)
      │                                       │
      ├─── Winner (CAS 成功: old == Mark_Old) └─── Loser (CAS 失败: old != Mark_Old)
      │                                       │
      │   Mark Word 变为 Inflated(M1)          │   发现 Mark Word 已被改写
      ├─► 返回 M1 并执行 monitor->enter()      ├─► 调用 om_release(M2) 归还内存
      │                                       ├─► 进入下一次 for(;;) 循环
      │                                       ├─► 命中 mark.has_monitor()
      │                                       └─► 读取并返回 M1,复用 T1 的 Monitor!

  • 硬件级原子性保障:在 x86_64 架构下,object->cas_set_mark 会展开为带 lock cmpxchg 前缀的物理汇编指令。缓存一致性协议(MESI)保证了针对同一对象内存地址的写操作会被精确串行化,绝不可能出现两个线程同时 CAS 成功的情况。

场景二:膨胀线程 (Inflator) 与锁释放线程 (Unlocker) 的 Racing 冲突

假设线程 T1 正在对 Object-Y 执行膨胀,而持有 Object-Y 轻量锁的线程 T2 恰好在此刻调用 fast_unlock 试图释放锁。

Race A:锁释放线程 T2 抢先 CAS 成功
  1. T2 执行 fast_unlock,用 cmpxchg 将 Mark Word 从 Fast-Locked (000) 还原为 Unlocked (001)
  2. T1 随后执行 cas_set_mark(M1, Fast-Locked),由于比较基准 Fast-Locked 已被修改为 Unlocked,T1 的 CAS 宣告失败
  3. T1 触发 om_release(M1) 归还 Monitor,并在下一次自旋中读取到最新的 Unlocked (001) Mark Word。
  4. T1 重新以 Unlocked 对象的逻辑尝试膨胀,再次发起 CAS。
Race B:膨胀线程 T1 抢先 CAS 成功
  1. T1 抢先将 Mark Word 从 Fast-Locked (000) 修改为 Inflated (M1)
  2. T2 执行 fast_unlock,尝试用 CAS 将 Mark Word 从 Fast-Locked 改回 Unlocked。因为 Mark Word 已变成 Inflated (10),T2 的 CAS 物理失败
  3. T2 的 JIT 指令流发现 CAS 失败后,自动跳转至慢速路径 LightweightSynchronizer::exit
  4. LightweightSynchronizer::exit 内部,T2 识别出 Mark Word 已经是 Inflated 状态,于是转为调用 M1->exit(current) 走重量级锁释放流程,递减 ObjectMonitor 内部的重入计数或唤醒 cxq/EntryList 中的等待线程。
// src/hotspot/share/runtime/lightweightSynchronizer.cpp
void LightweightSynchronizer::exit(oop object, current) {
  markWord mark = object->mark();

  if (mark.has_monitor()) {
    // 锁释放时发现已被抢先膨胀,退化转交 ObjectMonitor 处理
    ObjectMonitor* monitor = mark.monitor();
    monitor->exit(current);
    return;
  }
  ...
}

场景三:膨胀完成后的 Owner 关系挂接 (inflate_and_enter)

在 JDK 21 的 -XX:+UseLightweightLocking 下,锁膨胀仅仅完成了 Mark Word 到 ObjectMonitor 的物理指针绑定,并不等同于当前线程已经成功获取了 ObjectMonitor 的持有权

核心调用的二次绑定 (lightweightSynchronizer.cpp)
void LightweightSynchronizer::inflate_and_enter(Handle obj, BasicLock* lock, JavaThread* current) {
  // Step 1: 执行膨胀(内部完成上述多线程 CAS 竞争裁决)
  ObjectMonitor* monitor = ObjectSynchronizer::inflate(current, obj(), inflate_cause_vm_internal);

  // Step 2: 显式调用 ObjectMonitor::enter() 竞争 Monitor 所有权
  monitor->enter(current);
}

monitor->enter(current) 内部,会将 _owner 设为当前线程(或递减/递增重入计数)。如果原始锁是被其他线程持有的 Fast-Locked 状态,ObjectMonitor 内部会通过 try_set_owner_from 与自旋机制,安全地接管所有权或将当前线程放入 _cxq 队列挂起。

场景四:om_allocateom_release 的无锁内存管理

为了防止高并发膨胀时频发调用 C-Heap malloc 导致内存碎片与分配瓶颈,ObjectMonitor 的分配采用三级缓存池设计:

  1. Thread-Local Free List (_om_free_list):每个 JavaThread 私有的无锁 Monitor 链表。
  2. Global Free List (g_free_list):全局无锁 Monitor 共享池,使用 CAS 进行无锁 Pop/Push 操作。
  3. C-Heap 分配:当前两级均无可用实例时,批量申请 C-Heap 内存。

在膨胀 CAS 失败(Loser)时,om_release 会立即将预分配的 Monitor 清空并放回当前线程的 _om_free_list 链表顶端,整个过程不涉及全局锁,性能极高。


4. 总结:JDK 21 锁膨胀状态转移表

初始 Mark Word 状态并发事件 / 竞争操作CAS 状态转移结果失败线程 (Loser) 的后续行为
Fast-Locked (000)多线程同时调用 inflateFast-Locked → \rightarrow Inflated (10)归还预分配 Monitor,自旋复用已挂载的 Monitor
Fast-Locked (000)线程 A inflate vs 线程 B fast_unlock若 A 胜:Fast-Locked → \rightarrow Inflated (10)


若 B 胜:Fast-Locked → \rightarrow Unlocked (001) | A 胜:B 走 ObjectMonitor::exit


B 胜:A 以 Unlocked 状态重新尝试膨胀 |
| Unlocked (001) | 多线程同时调用 inflate | Unlocked → \rightarrow Inflated (10) | 归还预分配 Monitor,自旋复用已挂载的 Monitor |
| Inflated (10) | 后续任何线程调用 inflate | 状态不变(直接命中 has_monitor()) | 无 CAS 操作,直接返回 mark.monitor() 指针 |

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值