深入理解 synchronized 底层实现:从 HotSpot C++ 源码看对象锁与 Monitor 机制

深入理解 synchronized 底层实现:从 HotSpot C++ 源码看对象锁与 Monitor 机制

日期 :2026-05-12
标签 :Java并发、JVM源码、synchronized、HotSpot、ObjectMonitor、锁优化
阅读建议:配合 OpenJDK 8/11 源码阅读效果更佳


一、前言

synchronized 是 Java 中最基础的线程同步关键字,几乎每个 Java 开发者都用过。但你是否思考过:

  • 为什么任意一个 Java 对象都能作为锁?
  • synchronized 加锁解锁,JVM 底层到底做了什么?
  • 偏向锁、轻量级锁、重量级锁的"升级"过程,在 C++ 代码中是如何体现的?
  • wait()/notify() 的等待队列,底层数据结构长什么样?

本文将深入 HotSpot VM 的 C++ 源码 ,从 oopDescmarkWordObjectMonitor 三个核心结构出发,彻底揭开 synchronized 的底层面纱。


二、对象头的秘密:为什么每个对象都能当锁?

2.1 对象内存布局

在 HotSpot 中,Java 对象在堆内存中的结构如下:

复制代码
┌─────────────────────────────────────┐
│           Object Header (对象头)      │  ← 锁信息在这里!
│  ┌─────────────────────────────┐   │
│  │      Mark Word (64 bits)     │   │  ← 锁状态、哈希码、GC年龄
│  │  ┌─────┬────┬────┬─────────┐ │   │
│  │  │unused│age │lock│biased_lock│ │   │
│  │  │(54)  │(4) │(2) │  (1)    │ │   │
│  │  └─────┴────┴────┴─────────┘ │   │
│  └─────────────────────────────┘   │
│  ┌─────────────────────────────┐   │
│  │      Klass Pointer (64 bits) │   │  ← 指向类元数据
│  └─────────────────────────────┘   │
├─────────────────────────────────────┤
│           Instance Data (实例数据)    │  ← 字段值
├─────────────────────────────────────┤
│           Padding (对齐填充)           │  ← 8字节对齐
└─────────────────────────────────────┘

关键发现 :对象头中的 Mark Word2 个 bit 存储锁状态,这意味着锁能力是对象与生俱来的,不是后天附加的。

2.2 MarkWord 的 C++ 定义

源码路径src/hotspot/share/oops/markWord.hpp

cpp 复制代码
class markWord {
 private:
  uintptr_t _value;  // 64 位整型,存储所有状态信息

 public:
  // 锁状态的枚举值(最后 2-3 位)
  enum { 
    locked_value             = 0,   // 00: 轻量级锁(指向栈帧中的 Lock Record)
    unlocked_value           = 1,   // 01: 无锁
    monitor_value            = 2,   // 10: 重量级锁(指向 ObjectMonitor)
    marked_value             = 3,   // 11: GC 标记(CMS 用)
    biased_lock_pattern      = 5    // 101: 偏向锁
  };

  // 位域偏移量定义
  enum {
    age_bits                 = 4,   // GC 分代年龄
    lock_bits                = 2,   // 锁状态标志位
    biased_lock_bits         = 1,   // 偏向锁标志位
    max_hash_bits            = 31,  // 无锁时的哈希码位数
  };

  // 判断当前锁状态的方法
  bool is_locked()   const { 
    return (mask_bits(value(), lock_mask_in_place) != unlocked_value); 
  }

  bool is_unlocked() const { 
    return (mask_bits(value(), lock_mask_in_place) == unlocked_value); 
  }

  bool has_bias_pattern() const { 
    return (mask_bits(value(), biased_lock_mask_in_place) == biased_lock_pattern); 
  }

  bool has_monitor() const {  // 是否是重量级锁?
    return ((value() & monitor_value) != 0);
  }

  // 获取重量级锁的 Monitor 指针(去掉最后 3 位标记位)
  ObjectMonitor* monitor() const {
    assert(has_monitor(), "must be heavyweight lock");
    return (ObjectMonitor*) (value() & ~monitor_value);
  }
};

核心逻辑

  • 无锁时(01):Mark Word 存储对象的 hashCode + GC 年龄
  • 轻量级锁时(00):Mark Word 指向线程栈中的 Lock Record
  • 重量级锁时(10):Mark Word 指向堆中的 ObjectMonitor
  • 偏向锁时(101):Mark Word 存储偏向的线程 ID + 时间戳

三、ObjectMonitor:重量级锁的"心脏"

3.1 数据结构定义

源码路径src/hotspot/share/runtime/objectMonitor.hpp

cpp 复制代码
class ObjectMonitor {
 public:
  // 操作结果枚举
  enum { OM_OK, OM_INTERRUPTED, OM_TIMED_OUT };

 private:
  // ========== 核心字段 ==========

  volatile markWord _header;        // 加锁前的原始 Mark Word(备份)
  volatile intptr_t _count;         // 进入 Monitor 的次数(配合 _recursions)
  volatile intptr_t _waiters;       // 调用 wait() 的线程数(等待池大小)
  volatile intptr_t _recursions;    // 锁的重入次数(synchronized 可重入的关键!)

  // ========== 线程队列指针 ==========

  ObjectThread* volatile _owner;    // ←【核心】当前持有锁的线程!

  ObjectWaiter* volatile _WaitSet;  // ←【等待池】调用 wait() 后进入此队列
                                    //   线程状态:WAITING/TIMED_WAITING

  ObjectWaiter* volatile _EntryList;// ←【阻塞池】竞争锁失败的线程队列
                                    //   线程状态:BLOCKED

  Thread* volatile _succ;           // 被唤醒的"继承者"线程(减少竞争)
  Thread* volatile _Responsible;    // 负责唤醒的线程(优化用)

  // 自旋相关字段(优化性能)
  int _Spinner;                     // 自旋次数计数
  int _SpinFreq;                    // 自旋频率
  int _SpinClock;                   // 自旋时钟

  // 缓存行填充(防止伪共享)
  int _pad[1];

 public:
  // ========== 核心方法 ==========

  void enter(TRAPS);                // 【加锁入口】获取锁,失败则阻塞
  void exit(bool notify);           // 【解锁】释放锁,唤醒等待线程

  void wait(jlong millis, bool interruptable, TRAPS);  // 【等待】释放锁并进入 WaitSet
  void notify(TRAPS);               // 【唤醒】从 WaitSet 移一个线程到 EntryList
  void notifyAll(TRAPS);            // 【唤醒全部】移所有线程到 EntryList

  // CAS 尝试快速获取锁(轻量级锁膨胀前)
  bool try_enter(Thread* THREAD);

  // 检查是否当前线程持有锁
  bool enterI(TRAPS);               // 膨胀后的慢路径入口
  void EnterI(TRAPS);               // 真正的进入逻辑(自旋 + 阻塞)
};

3.2 关键结构图示

复制代码
┌─────────────────────────────────────────┐
│           ObjectMonitor (C++ 对象)        │
│  ┌─────────────────────────────────────┐ │
│  │  _owner = Thread-A 指针            │ │  ← 谁持有锁?
│  │  _recursions = 2                    │ │  ← 重入了 2 次
│  │  _header = [原始 Mark Word 备份]    │ │  ← 解锁时恢复
│  └─────────────────────────────────────┘ │
│                                         │
│  ┌──────────────┐    ┌──────────────┐    │
│  │   _WaitSet   │    │  _EntryList  │    │
│  │   (等待池)    │    │   (阻塞池)    │    │
│  │              │    │              │    │
│  │  Thread-B ──→│    │  Thread-C ──→│    │
│  │  (调用wait)   │    │  (竞争失败)   │    │
│  │  Thread-D ──→│    │  Thread-E ──→│    │
│  │  (调用wait)   │    │              │    │
│  │   WAITING    │    │   BLOCKED    │    │
│  └──────────────┘    └──────────────┘    │
│                                         │
│  wait() → 进 _WaitSet                   │
│  notify() → 从 _WaitSet 移到 _EntryList │
│  锁释放 → 从 _EntryList 挑一个唤醒       │
└─────────────────────────────────────────┘

四、加锁流程:从字节码到 C++ 的完整链路

4.1 Java 层

java 复制代码
public synchronized void method();  // ACC_SYNCHRONIZED 标志位

public void block() {
    synchronized (obj) {            // monitorenter + monitorexit 字节码
        // ...
    }
}

4.2 字节码层

复制代码
// javap -c 查看字节码
monitorenter   // 进入同步块:调用 InterpreterRuntime::monitorenter
// ... 业务代码 ...
monitorexit    // 退出同步块:调用 InterpreterRuntime::monitorexit
// 可能多个 monitorexit(异常路径)

4.3 C++ 层:InterpreterRuntime::monitorenter

源码路径src/hotspot/share/interpreter/interpreterRuntime.cpp

cpp 复制代码
// 解释器执行 monitorenter 时的入口
IRT_ENTRY_NO_ASYNC(void, InterpreterRuntime::monitorenter(JavaThread* thread, BasicObjectLock* elem))

  assert(Universe::heap()->is_in_reserved(elem->obj()), "must be heap object");

  // 获取要锁定的对象
  Handle h_obj(thread, elem->obj());

  // 【关键调用】进入 ObjectSynchronizer 的同步逻辑
  ObjectSynchronizer::enter(h_obj, elem->lock(), CHECK);

  assert(Universe::heap()->is_in_reserved(elem->obj()), "must be heap object");

IRT_END

4.4 C++ 层:ObjectSynchronizer::enter(核心!)

源码路径src/hotspot/share/runtime/synchronizer.cpp

cpp 复制代码
// ========== 加锁主入口 ==========
void ObjectSynchronizer::enter(Handle obj, BasicLock* lock, JavaThread* thread) {

  markWord mark = obj->mark();  // 读取对象头的 Mark Word

  // ========================================
  // 路径1:无锁状态 → 尝试 CAS 加轻量级锁
  // ========================================
  if (mark.is_neutral()) {  // 判断最后 3 位是否为 001(无锁)

    // 构造 displaced header(把原始 Mark Word 复制到 Lock Record)
    markWord displaced = mark;

    // 把 Lock Record 地址 CAS 到 Mark Word 中
    // 预期值 = mark(无锁状态),新值 = 指向 Lock Record 的指针 + 00(轻量级锁标记)
    if (mark == obj->cas_set_mark(displaced, markWord::from_pointer(lock))) {

      // CAS 成功!轻量级锁加锁完成
      // 当前线程栈帧中创建了 Lock Record,存储原始 Mark Word
      return;
    }
    // CAS 失败:说明有其他线程竞争,继续往下走
  }

  // ========================================
  // 路径2:检查是否重入(当前线程已持有轻量级锁)
  // ========================================
  if (mark.has_locker() && thread->is_lock_owned((address)mark.locker())) {

    // 是当前线程自己持有的锁,重入!
    // 把 Lock Record 的 displaced header 设为 NULL(表示重入)
    lock->set_displaced_header(markWord::from_pointer(NULL));

    // _recursions++ 在 is_lock_owned 中处理
    return;
  }

  // ========================================
  // 路径3:锁膨胀 → 升级为重量级锁
  // ========================================
  // 走到这里说明:
  // 1. 不是无锁(已经被其他线程锁定)
  // 2. 不是当前线程的重入
  // 3. 需要膨胀为 ObjectMonitor,然后阻塞等待

  // 膨胀:创建或获取 ObjectMonitor
  ObjectMonitor* monitor = inflate(thread, obj);

  // 进入 Monitor 的竞争逻辑(可能自旋,可能阻塞)
  monitor->enter(thread);
}

4.5 C++ 层:ObjectMonitor::enter(重量级锁入口)

源码路径src/hotspot/share/runtime/objectMonitor.cpp

cpp 复制代码
void ObjectMonitor::enter(TRAPS) {
  JavaThread* const self = THREAD;

  // --- 快速路径1:CAS 尝试获取锁 ---
  void* cur = Atomic::cmpxchg_ptr(self, &_owner, (void*)NULL);
  if (cur == NULL) {
    // CAS 成功!从无锁/释放状态直接获得锁
    assert(_recursions == 0, "invariant");
    assert(_owner == self, "invariant");
    return;  // 快速返回,无阻塞!
  }

  // --- 快速路径2:重入 ---
  if (cur == self) {
    _recursions++;  // 重入次数 +1
    return;         // 重入成功,无阻塞!
  }

  // --- 快速路径3:自旋尝试(多核 CPU 才有意义)---
  if (TrySpin(self) > 0) {
    assert(_owner == self, "must be");
    assert(_recursions == 0, "must be");
    return;  // 自旋成功,无阻塞!
  }

  // --- 慢路径:阻塞等待 ---
  // 走到这里,说明锁被其他线程持有,且自旋失败
  // 当前线程要进入 _EntryList 阻塞,等待被唤醒

  EnterI(self);  // 进入复杂的阻塞逻辑
}

4.6 C++ 层:EnterI(真正的阻塞逻辑)

cpp 复制代码
void ObjectMonitor::EnterI(TRAPS) {
  JavaThread* self = THREAD;

  // 构造 ObjectWaiter 节点(当前线程的封装)
  ObjectWaiter node(self);
  node.set_state(ObjectWaiter::TS_CXQ);  // 先放到 CXQ(竞争队列)

  // 把节点插入 _cxq 队列头部(LIFO 栈结构)
  ObjectWaiter* nxt = _cxq;
  for (;;) {
    node._next = nxt;
    if (Atomic::cmpxchg_ptr(&node, &_cxq, nxt) == nxt) break;
    nxt = _cxq;  // CAS 失败,重试
  }

  // --- 自旋优化:在阻塞前再尝试几次 ---
  if (TryLock(self) > 0) {
    // 自旋成功!直接获得锁,从队列移除
    return;
  }

  // --- 正式阻塞:调用 OS 的 park 方法 ---
  // 对应 Java 的 LockSupport.park()
  for (;;) {
    if (TryLock(self) > 0) break;  // 最后再试一次

    // 线程状态改为 BLOCKED
    ThreadBlockInVM tbivm(self);

    // 调用 OS 层阻塞(Linux: futex, Windows: WaitForSingleObject)
    self->_ParkEvent->park();  // ← 这里真正挂起线程!

    // 被唤醒后,继续循环尝试获取锁
  }

  // 获得锁后,从队列移除自己
  UnlinkAfterAcquire(self, &node);

  // 重入次数设为 1
  _recursions = 0;
  _count++;
}

五、解锁流程:ObjectMonitor::exit

cpp 复制代码
void ObjectMonitor::exit(bool not_suspended, TRAPS) {
  JavaThread* self = THREAD;

  // --- 检查:必须是锁的持有者才能解锁 ---
  if (_owner != self) {
    // 可能是重入次数没减完,或者是未持有锁就调用 unlock(异常)
    if (_recursions != 0) {
      // 重入次数还没减到 0,继续减
      _recursions--;
      return;  // 只是减少重入计数,不真正释放
    }
    // 否则抛出 IllegalMonitorStateException
  }

  // --- 重入递减 ---
  if (_recursions != 0) {
    _recursions--;
    return;  // 还有外层 synchronized,不真正释放
  }

  // --- 真正释放锁 ---
  _owner = NULL;  // 清空持有者
  _count--;       // 进入次数减1

  // --- 唤醒等待线程(如果有的话)---
  // 策略:从 _EntryList 或 _cxq 中挑一个线程唤醒
  // 使用 "启发式" 算法,尽量保证公平性和吞吐量平衡

  ObjectWaiter* w = _EntryList;
  if (w != NULL) {
    // 从 EntryList 唤醒一个
    ExitEpilog(self, w);  // 调用 unpark(w->_thread)
    return;
  }

  // EntryList 为空,从 cxq 移动一些到 EntryList
  w = _cxq;
  if (w != NULL) {
    // 把 cxq 的节点倒序放入 EntryList
    // 然后唤醒第一个
    ExitEpilog(self, w);
    return;
  }

  // 没有等待线程,直接返回(锁已释放)
}

六、锁膨胀:inflate 的 C++ 实现

cpp 复制代码
ObjectMonitor* ObjectSynchronizer::inflate(Thread* self, oop object) {

  for (;;) {
    markWord mark = object->mark();

    // --- 情况1:已经是重量级锁 ---
    if (mark.has_monitor()) {
      return mark.monitor();  // 直接返回已有的 Monitor
    }

    // --- 情况2:正在膨胀中(其他线程在膨胀)---
    if (mark == markWord::INFLATING()) {
      // 忙等待,让出 CPU
      continue;  // 自旋重试
    }

    // --- 情况3:轻量级锁(有 Lock Record)---
    if (mark.has_locker()) {
      // 分配新的 ObjectMonitor
      ObjectMonitor* monitor = new ObjectMonitor();

      // 保存原始 Mark Word(解锁时要恢复)
      monitor->set_header(mark);

      // 设置 owner 为持有轻量级锁的线程
      monitor->set_owner(mark.locker()->owner_thread());

      // 把对象头的 Mark Word 替换为 Monitor 指针(INFLATING 中间态)
      markWord cmp = object->cas_set_mark(markWord::INFLATING(), mark);

      if (cmp == mark) {  // CAS 成功
        // 把 Monitor 地址写入对象头
        object->set_mark(markWord::encode(monitor));
        return monitor;
      }

      // CAS 失败,释放 Monitor,重试
      delete monitor;
      continue;
    }

    // --- 情况4:无锁状态 ---
    // 直接分配 Monitor,设置为无竞争状态
    ObjectMonitor* monitor = new ObjectMonitor();
    monitor->set_header(mark);
    monitor->set_owner(NULL);

    if (object->cas_set_mark(markWord::encode(monitor), mark) == mark) {
      return monitor;
    }

    delete monitor;
    continue;  // CAS 失败,重试
  }
}

七、wait/notify 的底层实现

7.1 wait() → ObjectMonitor::wait

cpp 复制代码
void ObjectMonitor::wait(jlong millis, bool interruptable, TRAPS) {
  JavaThread* self = THREAD;

  // 1. 检查:必须是锁持有者才能调用 wait
  if (_owner != self) {
    throw IllegalMonitorStateException();
  }

  // 2. 构造等待节点
  ObjectWaiter node(self);
  node.set_state(ObjectWaiter::TS_WAIT);  // WAITING 状态

  // 3. 加入 _WaitSet(等待池)
  AddWaiter(&node);

  // 4. 释放锁(和 Object.wait() 语义一致:释放锁并等待)
  exit(true, self);  // 解锁!

  // 5. 线程状态改为 WAITING 或 TIMED_WAITING
  ThreadState old_state = self->set_thread_state(_thread_blocked);

  // 6. 阻塞等待被唤醒(或超时、中断)
  if (interruptable && Thread::is_interrupted(self, true)) {
    // 被中断了
  }

  // 7. 被唤醒后,重新竞争锁(进入 _EntryList 或 _cxq)
  EnterI(self);  // 和 enter 一样,要重新抢锁!

  // 8. 从 _WaitSet 移除
  node.remove();
}

7.2 notify() → ObjectMonitor::notify

cpp 复制代码
void ObjectMonitor::notify(TRAPS) {
  // 检查:必须是锁持有者
  if (_owner != THREAD) {
    throw IllegalMonitorStateException();
  }

  if (_WaitSet == NULL) return;  // 没有等待线程

  // 从 _WaitSet 头部取一个线程
  ObjectWaiter* iterator = _WaitSet;
  _WaitSet = iterator->_next;  // 链表移除

  // 移到 _EntryList(或 _cxq),让它参与锁竞争
  // 注意:不是直接给锁,而是让它去竞争!
  iterator->set_state(ObjectWaiter::TS_ENTER);

  // 插入 EntryList
  iterator->_next = _EntryList;
  _EntryList = iterator;

  // 唤醒线程(unpark),但唤醒后还要竞争锁
  // 这就是为什么 notify 后不会立刻执行,要等当前线程释放锁
}

八、锁优化的 C++ 实现

8.1 偏向锁撤销(RevokeBias)

cpp 复制代码
// 当另一个线程尝试获取已偏向的锁时,触发撤销
void BiasedLocking::revoke_at_safepoint(Handle obj) {
  markWord mark = obj->mark();

  if (mark.has_bias_pattern()) {
    // 把偏向锁改为无锁状态(001)
    markWord prototype = obj->klass()->prototype_header();
    markWord unbiased = markWord::encode(mark.hash(), mark.age(), prototype.lock_bits());

    obj->set_mark(unbiased);  // 撤销偏向锁!

    // 后续竞争会走轻量级锁/重量级锁逻辑
  }
}

8.2 锁消除(Lock Elimination)

不是 C++ 运行时逻辑,而是 JIT 编译器(C2) 的优化:

cpp 复制代码
// C2 编译器的逃逸分析后,如果发现锁对象不会逃逸出线程
// 直接去掉 monitorenter/monitorexit

// 例如:
public void method() {
    Object lock = new Object();  // 局部变量,不逃逸
    synchronized (lock) {         // C2 会消除这个锁!
        // ...
    }
}

九、总结:一张图看懂 synchronized 底层

复制代码
Java 层:synchronized(obj) / synchronized 方法
    ↓ 编译为字节码
字节码:monitorenter + monitorexit (+ ACC_SYNCHRONIZED)
    ↓ 解释器/JIT 调用
C++ 层:InterpreterRuntime::monitorenter
    ↓
ObjectSynchronizer::enter(obj, lock, thread)
    ↓ 读取 Mark Word 判断状态
┌─────────────────────────────────────────┐
│  无锁(001)? ──→ CAS 加轻量级锁 ──→ 成功返回  │
│     ↓ 失败                               │
│  轻量级锁(00)? ──→ 检查是否重入 / 膨胀为 Monitor │
│     ↓ 被其他线程持有                        │
│  重量级锁(10)? ──→ ObjectMonitor::enter     │
│     ↓                                     │
│  进入 _EntryList / _cxq 队列 ──→ 自旋尝试    │
│     ↓ 自旋失败                             │
│  OS 阻塞:pthread_mutex_lock / futex / ParkEvent│
│     ↓ 被唤醒                               │
│  重新竞争锁 ──→ 成功 → 成为 _owner          │
└─────────────────────────────────────────┘
    ↓
执行业务代码
    ↓
monitorexit / 方法返回
    ↓
ObjectMonitor::exit()
    ↓
_recursions-- / _owner = NULL
    ↓
从 _EntryList 唤醒下一个线程(unpark)

十、面试高频问题

问题 答案
为什么任意对象都能当锁? 对象头 Mark Word 有 2 bit 锁状态位,锁能力是对象与生俱来的
synchronized 是可重入锁吗? 是,底层 _recursions 字段记录重入次数
锁升级能降级吗? 只能升级(无锁→偏向→轻量→重量),不能降级(除锁粗化等优化外)
wait 为什么要放在 while 里? 防止虚假唤醒(spurious wakeup),醒来后要重新检查条件
notify 和 notifyAll 区别? notify 只移一个线程到 EntryList;notifyAll 移所有,但都要竞争锁
偏向锁为什么 JDK 15 后废弃? 维护成本高,多线程竞争下撤销偏向锁的开销反而更大

参考源码路径(OpenJDK 8/11)

复制代码
src/hotspot/share/oops/oop.hpp              # 对象头定义
src/hotspot/share/oops/markWord.hpp         # Mark Word
src/hotspot/share/runtime/objectMonitor.hpp # 监视器定义
src/hotspot/share/runtime/objectMonitor.cpp # 监视器实现
src/hotspot/share/runtime/synchronizer.cpp  # 同步器(enter/exit/inflate)
src/hotspot/share/interpreter/interpreterRuntime.cpp  # 字节码解释入口

版权声明:转载请附上原文出处链接和本声明。

相关推荐
2401_832298101 小时前
AI智能体监管落地,OpenClaw率先建立行业合规标准
开发语言
Szime1 小时前
深智微IC华润微代理:MCU选型与工业控制方案推荐
c++
geovindu1 小时前
go: Lock/Mutex Pattern
开发语言·后端·设计模式·golang·互斥锁模式
知识分享小能手2 小时前
R语言入门学习教程,从入门到精通,R语言日期和时间序列(6)
开发语言·学习·r语言
叼烟扛炮2 小时前
C++ 知识点18 内部类
开发语言·c++·算法·内部类
汉克老师2 小时前
GESP5级C++考试语法知识(十五、分治算法(二))
c++·算法·排序算法·分治算法·gesp5级·gesp五级
TAN-90°-2 小时前
Java 3——getter和setter super()关键字
java·开发语言
wand codemonkey2 小时前
(二十七)Maven(依赖)【安装】+【项目结构】
java·开发语言·maven
linda公馆2 小时前
Maven项目报错:java:错误:不支持发行版本 5
java·开发语言·maven