JUC 中 synchronized 的底层实现原理解析——Monitor
本文深入解析了Java中的Monitor同步机制,主要包括:1. 核心概念:Monitor是对象级的同步工具,通过互斥锁和条件变量实现线程同步与协作,包含EntrySet、Owner、WaitSet等关键组件。2. 工作原理:详细描述了锁竞争流程(线程获取/释放锁)和线程协作流程(wait/notify机制),以及三种锁释放场景。3. 底层实现:分析对象头MarkWord与Monitor的关联,包
·
一、Monitor 核心概念
1.1 定义与核心作用
- 核心定义:Monitor(中文译为 “管程”)是一种同步机制,本质是对象的同步工具,用于保证同一时间仅有一个线程能访问共享资源的临界区,解决多线程并发下的竞态条件问题。
- 核心目标:
- 互斥性:限制临界区同一时间只能被一个线程执行(解决指令交错)。
- 协作性:支持线程间通过等待 / 通知机制(wait/notify)实现高效协作(如生产者 - 消费者模型)。
- 生活类比:Monitor 如同一个带锁的房间,临界区代码是房间内的资源,线程需先获取锁(钥匙)才能进入,房间内同一时间只能有一个人(线程),线程完成后释放锁,其他等待线程再竞争进入。
1.2 底层原理(Java 中的 Monitor 实现)
- 对象与 Monitor 的关联:Java 中每个对象都天生关联一个 Monitor(即 “对象锁” 的本质),通过对象头(Object Header)中的
Mark Word存储 Monitor 的关联信息(如锁状态、持有锁的线程 ID)。 - Monitor 的所属关系:
- 普通对象:创建对象时,JVM 自动为其分配对应的 Monitor(懒加载,首次竞争锁时初始化)。
- 类对象(Class):静态同步方法的锁本质是类对象的 Monitor。
- 底层依赖:Java 的 Monitor 基于操作系统的互斥量(Mutex)和条件变量(Condition Variable)实现,但通过 JVM 优化(如偏向锁、轻量级锁)避免直接调用操作系统内核态函数,减少性能开销。
1.3 Monitor 关键组成部分
| 组成部分 | 作用 |
|---|---|
| Entry Set(入口队列) | 等待获取锁的线程队列,线程竞争锁失败时进入此队列(阻塞状态:BLOCKED)。 |
| Owner(锁持有者) | 当前持有 Monitor 锁的线程,同一时间仅能有一个线程成为 Owner。 |
| Wait Set(等待队列) | 调用 wait() 后释放锁的线程队列(阻塞状态:WAITING/TIMED_WAITING)。 |
| 互斥锁(Mutex) | 保证临界区互斥访问的核心组件,控制线程进入临界区的权限。 |
| 条件变量(Condition) | 支持线程间协作,提供 wait()/notify()/notifyAll() 方法实现等待 - 通知。 |
二、Monitor 工作流程(线程竞争与协作)
图示:

2.1 锁竞争流程(Entry Set → Owner)
- 线程尝试进入临界区(执行
synchronized代码块 / 方法),首先尝试获取 Monitor 锁。 - 若 Monitor 无持有者(锁空闲):当前线程直接成为 Owner,标记
Mark Word为 “锁定状态”,执行临界区代码。 - 若 Monitor 已被其他线程持有(锁占用):当前线程进入 Entry Set,状态变为 BLOCKED,等待锁释放。
- 当 Owner 线程释放锁(正常退出临界区 / 异常 / 调用
wait()):JVM 从 Entry Set 中唤醒一个线程(非公平,默认策略),该线程重新竞争锁,成功后成为新的 Owner。
2.2 线程协作流程(Owner → Wait Set → Entry Set)
- Owner 线程执行过程中,若需等待某个条件满足(如生产者等待队列非空),调用
wait()方法:- 释放当前持有的 Monitor 锁,状态从 RUNNABLE 变为 WAITING(或 TIMED_WAITING,若调用
wait(long))。 - 线程进入 Wait Set,等待其他线程的通知。
- 释放当前持有的 Monitor 锁,状态从 RUNNABLE 变为 WAITING(或 TIMED_WAITING,若调用
- 其他线程(通常是 Owner 线程)完成操作后,调用
notify()或notifyAll()方法:notify():从 Wait Set 中随机唤醒一个线程,该线程移至 Entry Set,状态变为 BLOCKED,等待重新竞争锁。notifyAll():唤醒 Wait Set 中所有线程,所有线程均移至 Entry Set,竞争锁。
- 被唤醒的线程竞争锁成功后,重新成为 Owner,从
wait()方法返回,继续执行后续代码。
2.3 锁释放的三种场景
- 线程正常执行完临界区代码,自动释放锁,Owner 变为 null。
- 线程在临界区抛出未捕获异常,JVM 自动释放锁。
- 线程调用
wait()方法,主动释放锁,进入 Wait Set。
三、Java 对象头与 Monitor 关联
Java 中 Monitor 与对象紧密绑定,核心是通过对象头存储 Monitor 相关信息,接下来分析对象头结构与 Monitor 的关联逻辑。
1. 对象头的基本结构
Java 对象头由两部分组成(数组对象多一个 array length 字段):
| 组成部分 | 作用 |
|---|---|
| Mark Word | 存储对象的锁状态、Monitor 指针、HashCode、GC 年龄等核心信息(重点)。 |
| Klass Word | 指向对象的类元数据(如 Object.class),确认对象的类型。 |

2. Mark Word 与 Monitor 的绑定
Mark Word 是对象头的核心,其结构会随锁状态变化,其中 “重量级锁” 状态下会存储 Monitor 的指针(ptr_to_heavyweight_monitor),直接关联对象与 Monitor。
32 位 JVM Mark Word 结构
| 锁状态 | Mark Word 结构(32 位) | 核心存储内容 |
|---|---|---|
| 无锁(Normal) | hashcode(25) + age(4) + biased_lock(0) + 01 | 对象哈希码、GC 年龄 |
| 偏向锁(Biased) | thread(23) + epoch(2) + age(4) + biased_lock(1) + 01 | 持有偏向锁的线程 ID |
| 轻量级锁(Lightweight) | ptr_to_lock_record(30) + 00 | 线程栈中锁记录的指针 |
| 重量级锁(Heavyweight) | ptr_to_heavyweight_monitor(30) + 10 | Monitor 对象的指针(核心关联) |
| GC 标记(Marked) | 空 + 11 | 垃圾回收标记 |

64 位 JVM Mark Word 结构
| 锁状态 | Mark Word 结构(64 位) |
|---|---|
| 无锁(Normal) | unused(25) + hashcode(31) + unused(1) + age(4) + biased_lock(0) + 01 |
| 重量级锁 | ptr_to_heavyweight_monitor(62) + 10 |

3. 核心结论
- 当对象被
synchronized加锁且升级为重量级锁时,Mark Word 中存储 Monitor 的指针,实现对象与 Monitor 的一一对应。 - 锁升级的本质:Mark Word 结构的变化,从无锁→偏向锁→轻量级锁→重量级锁,Monitor 仅在重量级锁时被完全激活。
四、Monitor 与 synchronized 的深度绑定
4.1 synchronized 依赖 Monitor 实现同步
- synchronized 的锁本质:synchronized 是 Monitor 的 “语法糖”,其底层完全依赖 Monitor 机制实现:
- 同步代码块:通过
monitorenter指令获取 Monitor 锁,monitorexit指令释放锁(异常时 JVM 自动插入monitorexit)。 - 同步方法:通过方法的
ACC_SYNCHRONIZED标志位,调用方法时自动获取 / 释放 Monitor 锁(锁对象为this或类对象)。
- 同步代码块:通过
- 代码层面验证:
// 同步代码块(锁对象为 lock) static final Object lock = new Object(); synchronized (lock) { // 临界区:依赖 Monitor 保证互斥 counter++; } // 同步方法(锁对象为 this) public synchronized void increment() { // 临界区:底层是 Monitor 锁 counter++; }
4.2 Monitor 锁的状态升级(JVM 优化)
Java 为提升 Monitor 性能,引入了锁状态升级机制(无锁 → 偏向锁 → 轻量级锁 → 重量级锁),不同状态对应 Monitor 的不同实现方式:
| 锁状态 | 适用场景 | Monitor 实现方式 | 性能特点 |
|---|---|---|---|
| 无锁 | 无线程竞争 | 未初始化 Monitor | 无需锁开销 |
| 偏向锁 | 单线程重复获取锁 | Monitor 记录偏向线程 ID,无需竞争 | 几乎无锁开销(仅 CAS 操作) |
| 轻量级锁 | 少量线程交替竞争 | 线程通过 CAS 操作尝试获取锁,自旋等待 | 无内核态阻塞开销,适合短临界区 |
| 重量级锁 | 多线程同时竞争 | 依赖操作系统互斥量(Mutex)实现 Monitor | 阻塞 / 唤醒开销大,适合长临界区 |
- 核心逻辑:锁状态只能升级不能降级,通过自适应策略(如自旋次数、竞争强度)动态切换,平衡性能与并发安全性。
4.3 线程八锁与 Monitor 锁对象判定(面试重点)
Monitor 的核心是 “锁对象唯一”,线程八锁本质是判定 synchronized 对应的 Monitor 锁对象,以下是高频场景:
| 场景 | 代码示例 | 锁对象(Monitor) | 执行结果 | 核心结论 |
|---|---|---|---|---|
| 1 | 同一对象调用两个同步成员方法 | this(当前对象的 Monitor) | 串行执行(12 或 21) | 同一 Monitor 互斥 |
| 2 | 同一对象调用同步方法 + 非同步方法 | 同步方法锁 this,非同步方法无锁 | 非同步方法先执行 | 非同步方法不参与 Monitor 竞争 |
| 3 | 不同对象调用同步静态方法 | 类对象(Class 的 Monitor) | 串行执行 | 类对象的 Monitor 全局唯一 |
| 4 | 同一对象调用同步成员方法 + 同步静态方法 | 成员方法锁 this,静态方法锁 Class | 并行执行 | 两个不同的 Monitor,不互斥 |
五、Monitor 实战案例(基于等待 / 通知机制)
5.1 案例 1:线程安全计数器(Monitor 互斥性)
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class MonitorCounter {
// 共享资源
private int counter = 0;
// 锁对象(关联 Monitor)
private final Object lock = new Object();
// 自增方法(依赖 Monitor 保证原子性)
public void increment() {
synchronized (lock) { // monitorenter:获取 Monitor 锁
counter++;
log.debug("线程 {} 执行自增,counter = {}", Thread.currentThread().getName(), counter);
} // monitorexit:释放 Monitor 锁
}
public int getCounter() {
synchronized (lock) {
return counter;
}
}
public static void main(String[] args) throws InterruptedException {
MonitorCounter counter = new MonitorCounter();
// 3 个线程并发自增 1000 次
Thread t1 = new Thread(() -> {
for (int i = 0; i < 1000; i++) counter.increment();
}, "t1");
Thread t2 = new Thread(() -> {
for (int i = 0; i < 1000; i++) counter.increment();
}, "t2");
Thread t3 = new Thread(() -> {
for (int i = 0; i < 1000; i++) counter.increment();
}, "t3");
t1.start();
t2.start();
t3.start();
t1.join();
t2.join();
t3.join();
log.debug("最终 counter = {}", counter.getCounter()); // 稳定输出 3000
}
}
- 核心原理:通过
synchronized绑定 Monitor,保证counter++临界区同一时间仅一个线程执行,避免指令交错。
5.2 案例 2:生产者 - 消费者模型(Monitor 协作性)
import lombok.extern.slf4j.Slf4j;
import java.util.LinkedList;
import java.util.Queue;
@Slf4j
public class MonitorProducerConsumer {
// 任务队列(共享资源)
private final Queue<String> taskQueue = new LinkedList<>();
// 队列容量
private static final int CAPACITY = 3;
// 锁对象(关联 Monitor)
private final Object lock = new Object();
// 生产者:添加任务
public void produce(String task) throws InterruptedException {
synchronized (lock) {
// 队列满时,等待消费者消费(进入 Wait Set)
while (taskQueue.size() >= CAPACITY) {
log.debug("队列满,生产者 {} 等待", Thread.currentThread().getName());
lock.wait(); // 释放 Monitor 锁,进入 Wait Set
}
// 生产任务
taskQueue.offer(task);
log.debug("生产者 {} 生产任务:{},队列大小:{}", Thread.currentThread().getName(), task, taskQueue.size());
lock.notifyAll(); // 唤醒消费者(从 Wait Set 移至 Entry Set)
}
}
// 消费者:获取任务
public String consume() throws InterruptedException {
synchronized (lock) {
// 队列空时,等待生产者生产(进入 Wait Set)
while (taskQueue.isEmpty()) {
log.debug("队列空,消费者 {} 等待", Thread.currentThread().getName());
lock.wait(); // 释放 Monitor 锁,进入 Wait Set
}
// 消费任务
String task = taskQueue.poll();
log.debug("消费者 {} 消费任务:{},队列大小:{}", Thread.currentThread().getName(), task, taskQueue.size());
lock.notifyAll(); // 唤醒生产者(从 Wait Set 移至 Entry Set)
return task;
}
}
public static void main(String[] args) {
MonitorProducerConsumer pc = new MonitorProducerConsumer();
// 2 个生产者线程
for (int i = 0; i < 2; i++) {
int producerId = i;
new Thread(() -> {
try {
for (int j = 0; j < 5; j++) {
pc.produce("任务-" + producerId + "-" + j);
Thread.sleep(500); // 模拟生产耗时
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "生产者-" + producerId).start();
}
// 2 个消费者线程
for (int i = 0; i < 2; i++) {
int consumerId = i;
new Thread(() -> {
try {
for (int j = 0; j < 5; j++) {
pc.consume();
Thread.sleep(1000); // 模拟消费耗时
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "消费者-" + consumerId).start();
}
}
}
- 核心原理:利用 Monitor 的 Wait Set 和条件变量,实现生产者与消费者的协作:
- 队列满时,生产者调用
wait()释放锁,进入等待; - 队列空时,消费者调用
wait()释放锁,进入等待; - 一方完成操作后,调用
notifyAll()唤醒另一方,避免死等。
- 队列满时,生产者调用
六、相关知识点
6.1 Monitor 与 Lock(ReentrantLock)的对比
| 特性 | Monitor(synchronized 底层) | Lock(ReentrantLock) |
|---|---|---|
| 锁获取方式 | 隐式获取(monitorenter) |
显式获取(lock()) |
| 锁释放方式 | 隐式释放(monitorexit/ 异常自动释放) |
显式释放(unlock(),必须在 finally 中) |
| 可中断性 | 否(BLOCKED 状态不可中断) | 是(lockInterruptibly()) |
| 超时获取 | 否 | 是(tryLock(long)) |
| 公平锁支持 | 否(默认非公平,JDK 1.6+ 有自适应公平策略) | 是(构造函数指定 true) |
| 多条件变量 | 否(仅一个 Wait Set) | 是(newCondition(),多个 Condition 对应多个等待队列) |
| 锁状态查询 | 无直接 API(需通过线程状态判断) | 有(isLocked()/getHoldCount()) |
| 适用场景 | 简单同步场景(代码简洁) | 复杂同步场景(如超时、中断、多条件) |
6.2 Monitor 常见问题与避坑指南
- 虚假唤醒:
- 问题:
wait()可能在未被notify()/notifyAll()唤醒的情况下返回(JVM 底层实现导致)。 - 解决:必须用
while循环判断条件,而非if(如案例 2 中while (taskQueue.isEmpty())),唤醒后重新校验条件。
- 问题:
wait()/notify()必须在 synchronized 块中:- 原因:调用这两个方法前,线程必须持有 Monitor 锁(否则抛
IllegalMonitorStateException)。 - 逻辑:
wait()需要先释放锁,notify()需要唤醒等待线程竞争锁,均依赖 Monitor 上下文。
- 原因:调用这两个方法前,线程必须持有 Monitor 锁(否则抛
notify()与notifyAll()的选择:notify():随机唤醒一个线程,可能导致其他线程长期等待(饥饿)。notifyAll():唤醒所有等待线程,避免饥饿,但可能增加锁竞争开销。- 建议:多线程协作时优先用
notifyAll(),除非能明确保证唤醒单个线程即可满足条件。
- 锁膨胀与性能影响:
- 问题:Monitor 从偏向锁升级为重量级锁后,性能开销大幅增加(内核态阻塞 / 唤醒)。
- 优化:尽量减小临界区代码长度,避免长时间持有锁;避免多线程同时竞争同一把锁。
6.3 Monitor 的核心特性总结
- 互斥性:通过 Owner 机制保证临界区独占访问,解决竞态条件。
- 可重入性:同一线程可多次获取同一 Monitor 锁(
Mark Word记录锁重入次数),避免死锁。 - 协作性:通过 Wait Set 和条件变量支持线程间高效通信,无需轮询(降低 CPU 开销)。
- 原子性保障:临界区代码在 Monitor 保护下,视为原子操作(避免指令交错)。
6.4 底层字节码分析(Monitor 指令级实现)
以同步代码块为例,查看 monitorenter 和 monitorexit 指令:
// 原代码
synchronized (lock) {
counter++;
}
// 编译后的字节码(关键部分)
0: aload_1 // 加载锁对象 lock 到操作数栈
1: monitorenter // 获取 Monitor 锁(进入临界区)
2: aload_0 // 加载当前对象
3: dup // 复制对象引用
4: getfield // 获取 counter 字段
7: iconst_1 // 压入常量 1
8: iadd // counter + 1
9: putfield // 赋值给 counter
12: aload_1 // 加载锁对象
13: monitorexit // 释放 Monitor 锁(正常退出)
14: goto 22 // 跳至方法结束
17: astore_2 // 捕获异常
18: aload_1 // 加载锁对象
19: monitorexit // 释放 Monitor 锁(异常退出)
20: aload_2 // 抛出异常
21: athrow
22: return
- 关键:
monitorenter对应锁获取,monitorexit对应锁释放,异常时 JVM 自动插入monitorexit,保证锁一定释放。
七、核心 API 详解(Monitor 相关)
| API 方法 | 作用 | 关键注意点 |
|---|---|---|
void wait() |
释放 Monitor 锁,进入 Wait Set(无超时等待) | 必须在 synchronized 块中;可能虚假唤醒,需配合 while 循环;唤醒后重新竞争锁 |
void wait(long timeout) |
释放 Monitor 锁,进入 TIMED_WAITING 状态(超时自动唤醒) | 超时时间单位为毫秒;其他注意点同 wait() |
void notify() |
从 Wait Set 中随机唤醒一个线程,移至 Entry Set 竞争锁 | 必须在 synchronized 块中;仅唤醒一个线程,可能导致饥饿 |
void notifyAll() |
唤醒 Wait Set 中所有线程,均移至 Entry Set 竞争锁 | 必须在 synchronized 块中;避免饥饿,多线程协作优先使用 |
synchronized 代码块 / 方法 |
隐式获取 / 释放 Monitor 锁 | 锁对象为指定对象 /this/ 类对象;自动重入;异常时自动释放锁 |
八、总结
Monitor 是 Java 并发编程的核心同步机制,是 synchronized 的底层实现基础,核心价值在于通过互斥性保证临界区安全,通过协作性支持线程间高效通信。
- 核心组成:Entry Set(锁竞争队列)、Owner(锁持有者)、Wait Set(等待队列);
- 核心流程:线程竞争锁(Entry Set → Owner)、线程协作(Owner → Wait Set → Entry Set);
- 关键拓展:锁状态升级(优化性能)、与 Lock 的对比、虚假唤醒避坑;
- 适用场景:简单同步用
synchronized(依赖 Monitor),复杂同步用Lock(灵活拓展)。
更多推荐


所有评论(0)