深入理解 Java 监视器(Monitor)、synchronized 实现原理与线程间通信机制
本文深入解析Java并发编程中的监视器(Monitor)机制及其实现原理。首先介绍Monitor作为同步原语的核心思想,说明其在Java中通过对象内置锁实现,包含Owner、EntrySet和WaitSet三个关键组件。然后剖析synchronized关键字的底层实现,包括字节码指令和JVM锁优化机制(偏向锁、轻量级锁、重量级锁)。最后详细讲解wait/notify线程通信机制的工作原理和使用注意
在 Java 并发编程中,synchronized 是最基础、最常用的同步手段,而其背后依赖的核心概念就是 监视器(Monitor)。很多开发者会用 synchronized,但对 Monitor 是什么?JVM 如何实现它?wait/notify 如何协作? 却一知半解。本文将从底层原理出发,系统讲解这三大核心知识点。
一、监视器(Monitor):Java 并发的基石
1. 什么是 Monitor?
Monitor(监视器) 是一种 同步原语,由 C.A.R. Hoare 在 1974 年提出,用于解决多线程对共享资源的互斥访问和线程协作问题。
✅ 核心思想:
- 同一时刻,只有一个线程能进入 Monitor 的临界区;
- 线程可以在 Monitor 内 等待某个条件成立,并被其他线程唤醒。
2. Java 中的 Monitor 模型
在 JVM 中,每个 Java 对象都关联一个 Monitor(也叫“内置锁”或“Intrinsic Lock”)。该 Monitor 包含三个关键组件:
| 组件 | 作用 |
|---|---|
| Owner | 当前持有锁的线程 |
| Entry Set(入口队列) | 等待获取锁的线程队列(阻塞状态) |
| Wait Set(等待队列) | 调用 wait() 后进入等待的线程队列 |
- 当线程执行
synchronized(obj)时,尝试获取obj的 Monitor; - 若 Monitor 未被占用,则线程成为 Owner;
- 若已被占用,则进入 Entry Set 阻塞;
- 若线程在同步块内调用
obj.wait(),则:- 释放 Monitor;
- 进入 Wait Set;
- 等待被
notify()唤醒后重新竞争锁。
🔑 关键点:
Monitor 是对象级别的,不是方法或代码块级别的。synchronized只是语法糖,真正锁的是对象。
二、synchronized 的底层实现原理
synchronized 在字节码层面由 monitorenter 和 monitorexit 指令实现。
1. 字节码示例
public void method() {
synchronized (this) {
// 临界区
}
}
编译后字节码:
public method()V
L0
aload_0
dup
astore_1
monitorenter // 获取 this 的 Monitor
L1
// 临界区代码
L2
aload_1
monitorexit // 释放 Monitor
L3
goto L4
L5
astore_2
aload_1
monitorexit // 异常时也释放锁
aload_2
athrow
L4
⚠️ 注意:JVM 会生成 两个
monitorexit,确保正常和异常路径都能释放锁。
2. JVM 层面的锁优化(JDK 6+)
早期 synchronized 性能差,因为直接依赖操作系统的 Mutex Lock(互斥锁),涉及用户态/内核态切换。JDK 6 引入了 锁升级机制:
锁状态演进(从低到高):
| 锁类型 | 适用场景 | 实现方式 |
|---|---|---|
| 无锁 | 无竞争 | 对象头 Mark Word 正常 |
| 偏向锁 | 单线程反复加锁 | Mark Word 存储线程 ID,无需 CAS |
| 轻量级锁 | 多线程交替执行 | CAS 自旋 + 栈帧 Lock Record |
| 重量级锁 | 多线程竞争激烈 | 转为 OS Mutex,线程阻塞 |
✅ 优势:
大部分场景下无需进入重量级锁,极大提升性能。
对象头 Mark Word 结构(32 位 JVM 示例):
| 锁状态 | Mark Word 内容 |
|---|---|
| 无锁 | hashcode | age |
| 偏向锁 | threadID | epoch | age | biased_bit |
| 轻量级锁 | 指向栈中 Lock Record 的指针 |
| 重量级锁 | 指向 Monitor 对象的指针 |
💡 Monitor 对象:当升级为重量级锁时,JVM 会在堆中创建一个
ObjectMonitor实例,并将对象头指向它。
三、线程间通信机制:wait / notify / notifyAll
synchronized 解决了 互斥,但线程协作需要 条件等待,这就是 wait()/notify() 的作用。
1. 通信流程(生产者-消费者模型)
private final Object lock = new Object();
private Queue<String> queue = new LinkedList<>();
private static final int MAX = 10;
// 生产者
public void produce(String item) throws InterruptedException {
synchronized (lock) {
while (queue.size() == MAX) {
lock.wait(); // 队列满,等待
}
queue.add(item);
lock.notifyAll(); // 通知消费者
}
}
// 消费者
public String consume() throws InterruptedException {
synchronized (lock) {
while (queue.isEmpty()) {
lock.wait(); // 队列空,等待
}
String item = queue.poll();
lock.notifyAll(); // 通知生产者
return item;
}
}
2. wait() / notify() 工作原理
| 方法 | 行为 |
|---|---|
wait() |
1. 释放当前对象的 Monitor 2. 将当前线程加入 Wait Set 3. 阻塞,直到被 notify 或中断 |
notify() |
从 Wait Set 中随机唤醒一个线程,移入 Entry Set |
notifyAll() |
唤醒 Wait Set 中所有线程,全部进入 Entry Set |
⚠️ 必须在 synchronized 块中调用!否则抛
IllegalMonitorStateException。
3. 为什么用 while 而不是 if?
防止 虚假唤醒(Spurious Wakeup) —— 操作系统可能在无 notify 的情况下唤醒线程。
// 错误写法
if (queue.isEmpty()) {
lock.wait(); // 被唤醒后可能仍为空!
}
// 正确写法
while (queue.isEmpty()) {
lock.wait(); // 唤醒后重新检查条件
}
四、总结:三大机制的关系图
Java 对象
│
└── 关联一个 Monitor(重量级锁时)
├── Owner:当前持有锁的线程
├── Entry Set:等待获取锁的线程(synchronized 竞争)
└── Wait Set:调用 wait() 的线程(线程协作)
↑
└── 通过 notify()/notifyAll() 唤醒
- synchronized → 控制 Entry Set,实现互斥;
- wait/notify → 操作 Wait Set,实现协作;
- Monitor → 作为底层容器,统一管理两者。
💬 面试金句:
“synchronized是门禁系统,只允许一人进入;wait/notify是对讲机,让里面的人可以喊话通知外面的人进来。而 Monitor,就是整栋大楼的安保中心。”
视频看了几百小时还迷糊?关注我,几分钟让你秒懂!(发点评论可以给博主加热度哦)
更多推荐


所有评论(0)