Java 中的 synchronized 关键字是实现线程安全与同步的核心手段,其底层依赖于 JVM 提供的 Monitor(管程 / 监视器锁) 机制。

Monitor 不仅在语义层保证了并发访问的互斥性可见性,更在底层的 Java 内存模型(JMM)与现代 CPU 硬件指令集上建立了严密的内存屏障与 Happens-Before 关联。

D2 Diagram
qtopie.github.io

JMM 内存模型与 Happens-Before 规则

Java 内存模型(JMM)通过 Happens-Before 原则定义了多线程环境下的内存可见性与有序性保障。Monitor 机制是 JMM Happens-Before 规则的核心载体之一。

监视器锁规则 (Monitor Lock Rule)

JMM 明确规定:对一个监视器锁的解锁(Unlock)Happens-Before 于后续对同一个监视器锁的加锁(Lock)。

D2 Diagram
qtopie.github.io

内存屏障与硬件级指令映射 (Memory Barriers)

为了实现 Monitor 锁的 Happens-Before 语义,JVM 在编译与字节码执行时会在 Monitor 的临界区边界插入硬件级内存屏障 (Memory Barrier)


Monitor 内部底层结构与锁膨胀机制

在 HotSpot JVM 中,每个对象都可以充当 Monitor 锁。对象的Mark Word(对象头) 在不同锁状态下指向不同的物理结构。

HotSpot ObjectMonitor C++ 结构解析

当锁膨胀为重量级锁(Heavyweight Lock)时,Mark Word 会指针关联到一个 C++ ObjectMonitor 结构体:

// HotSpot 源码 hotspot/src/share/vm/runtime/objectMonitor.hpp
ObjectMonitor() {
    _header       = NULL;
    _count        = 0;       // 重入计数器
    _waiters      = 0;
    _recursions   = 0;       // 锁重入次数
    _object       = NULL;    // 指向被锁的对象
    _owner        = NULL;    // 指向当前持有 Monitor 的 Thread 指针
    _WaitSet      = NULL;    // 处于 wait() 状态的线程单向链表
    _WaitSetLock  = 0;
    _Responsible  = NULL;
    _succ         = NULL;
    _cxq          = NULL;    // 竞争锁失败的单向 LIFO 栈 (_cxq)
    FreeNext      = NULL;
    _EntryList    = NULL;    // 准备竞争锁的双向等待队列
    _spinFreq     = 0;
    _spinClock    = 0;
    OwnerIsThread = 0;
}

Java Monitor 如何支持锁队列与 Condition 队列?

与 Java AQS(依赖 state + 双向 CLH 队列 + 单向 Condition 队列)的架构思想高度契合,JVM 底层的 ObjectMonitor(C++ 实现) 本质上也维护了两类物理队列来实现锁竞争(锁队列)线程协同(Condition 队列)

D2 Diagram
qtopie.github.io

1. 核心封装节点:ObjectWaiter

在 C++ 内部,JVM 并不是直接把原生 Thread 指针放入队列,而是把线程封装为一个 ObjectWaiter 结构体(类似于 AQS 的 Node):

class ObjectWaiter : public StackObj {
  public:
    enum TStates { TS_UNDEF, TS_READY, TS_RUN, TS_WAIT, TS_ENTER, TS_CXQ };
    ObjectWaiter* _next;
    ObjectWaiter* _prev;
    Thread*       _thread;
    volatile TStates TState;
    bool          _active;
    // ...
};

2. 锁队列机制:_cxq_EntryList

Java Monitor 的锁队列由 _cxq_EntryList 配合组成:

3. Condition 队列机制:_WaitSet

_WaitSet 是 JVM 用来实现 Object.wait()Object.notify() 的物理条件队列:

4. Monitor 队列 vs AQS 队列对比

对比维度JVM Monitor 底层实现Java AQS 框架实现
锁队列物理结构_cxq (单向 LIFO 栈) + _EntryList (双向队列)双向 CLH 队列 (head / tail)
Condition 队列_WaitSet (单向/双向链表)单向 ConditionObject 链表
Condition 数量每个 Monitor 对象仅支持 1 个 _WaitSet1 个 Lock 支持创建多个独立 Condition 队列
节点转移机制notify() 时将节点由 _WaitSet 移入 _EntryList / _cxqsignal() 时将节点由 Condition 队列移入双向 CLH 队列尾部
抢锁自旋与挂起CAS 压入 _cxq $\to$ os::PlatformEvent::park()CAS 压入 CLH 尾部 $\to$ LockSupport.park()

锁膨胀与升级机制 (Lock Escalation)

为了在低竞争场景下避免重量级锁昂贵的内核态切换(pthread_mutex / futex),HotSpot JVM 设计了锁膨胀升级机制:

D2 Diagram
qtopie.github.io
  1. 偏向锁(Biased Locking)
    • 假设“锁总是由同一个线程多次获得”。
    • 首次获取锁时,在 Mark Word 中记录 ThreadID;后续该线程再次进入时,仅需比对 ThreadID,完全无需 CAS 操作
  2. 轻量级锁(Lightweight Locking)
    • 当有第二个线程尝试获取锁时,偏向锁撤销并升级为轻量级锁。
    • 线程在各自的线程栈帧中创建 Lock Record,通过 CAS 将 Mark Word 指向 Lock Record。抢锁失败时采用适应性自旋等待,避免让出 CPU。
  3. 重量级锁(Heavyweight Locking)
    • 当自旋达到一定次数,或竞争过于激烈时,轻量级锁膨胀为重量级锁。
    • JVM 申请分配底层 C++ ObjectMonitor,争抢失败的线程由内核态调用 pthread_mutex / futex 挂起(park),进入阻塞休眠状态。

Monitor 在 Java 中的主要应用场景

1. 语言级同步块与同步方法 (synchronized)

2. 传统线程间协作 (Object.wait() / Object.notify())

3. Java 基础并发容器实现


Monitor 锁 vs AQS (ReentrantLock) 对比

对比维度JVM Monitor (synchronized)Java AQS (ReentrantLock)
实现层级JVM C++ 引擎与字节码 (monitorenter/exit)Java 语言层 SDK (JUC 包 Unsafe / VarHandle CAS)
底层锁膨胀支持 (无锁 $\to$ 偏向锁 $\to$ 轻量级锁 $\to$ 重量级锁)不支持 (依赖 state CAS 结合 LockSupport.park)
灵活度与控制力自动加锁/释放,隐式异常处理,灵活性受限手动 lock() / unlock(),支持 tryLock() 超时与中断
条件等待队列每个 Monitor 锁仅对应 1 个等待池 (_WaitSet)1 个 Lock 可创建 多个独立的 Condition 队列
公平性选择仅支持非公平锁原生支持公平锁非公平锁构造选择
Happens-Before自动建立 monitorexit $\to$ monitorenter 规则依靠 volatile state 变量读写建立 HB 规则