深入理解 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++ 源码 ,从 oopDesc、markWord、ObjectMonitor 三个核心结构出发,彻底揭开 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 Word 用 2 个 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 # 字节码解释入口
版权声明:转载请附上原文出处链接和本声明。