总结摘要
Java中Object中notify和wait方法线程同步原理
核心概念:监视器锁(Monitor)
Java 中每个对象都天然绑定一把监视器锁(Monitor / intrinsic lock)。wait / notify / notifyAll 正是基于这把锁来协调线程之间的通信。
1
2
3
4
5
6
7
8
9
10
11
12
13
| ┌─────────────────────────────────────────┐
│ Object Monitor │
│ │
│ ┌──────────────┐ ┌────────────────┐ │
│ │ Entry Set │ │ Wait Set │ │
│ │wait get lock │ │ exec wait func │ │
│ └──────┬───────┘ └───────┬────────┘ │
│ │ │ notify │
│ ▼ ▼ │
│ ┌──────────────────────────┐ │
│ │ Owner Thread │ │
│ └──────────────────────────┘ │
└─────────────────────────────────────────┘
|
三个核心方法
| 方法 | 作用 |
|---|
wait() | 释放锁,当前线程进入该对象的 Wait Set,挂起等待 |
notify() | 从 Wait Set 中随机唤醒一个线程,移入 Entry Set |
notifyAll() | 唤醒 Wait Set 中的全部线程,移入 Entry Set |
⚠️ 前提:这三个方法必须在 synchronized 块内调用,否则抛出 IllegalMonitorStateException
详细执行流程
1
2
3
4
5
6
7
8
9
10
11
| Thread-A (生产者) Thread-B (消费者)
───────────────── ─────────────────
synchronized(lock) { synchronized(lock) {
// 1. 获得锁 // 1. 尝试获得锁(阻塞在 Entry Set)
while(!条件满足) {
lock.wait(); ──────────────► // 2. A 释放锁,进入 Wait Set
} // B 获得锁,执行业务
// 5. 被唤醒后
// 重新竞争锁 lock.notifyAll(); ─► 3. 唤醒 Wait Set 线程
// 6. 获锁后继续执行 } // 4. B 退出 synchronized,释放锁
}
|
逐步拆解 wait() 的内部动作
1
2
3
4
5
6
7
8
9
10
| 调用 wait()
│
├─ 1. 检查当前线程是否持有该对象的 Monitor → 否则抛异常
├─ 2. 将当前线程加入对象的 Wait Set
├─ 3. 原子性地 释放 Monitor 锁
├─ 4. 线程挂起(操作系统层面 park)
│ ↑ 此处等待被唤醒
├─ 5. 收到 notify / 超时 / interrupt → 移出 Wait Set
├─ 6. 重新竞争 Monitor 锁(可能再次阻塞)
└─ 7. 获锁成功 → wait() 返回,继续执行
|
经典范例:生产者 - 消费者
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
| public class Buffer {
private final Queue<Integer> queue = new LinkedList<>();
private final int CAPACITY = 5;
// 生产者
public synchronized void produce(int item) throws InterruptedException {
// ✅ 用 while 而不是 if,防止虚假唤醒(spurious wakeup)
while (queue.size() == CAPACITY) {
System.out.println("缓冲区满,生产者等待...");
wait(); // 释放锁,进入 Wait Set
}
queue.offer(item);
System.out.println("生产: " + item);
notifyAll(); // 唤醒所有等待的消费者
}
// 消费者
public synchronized int consume() throws InterruptedException {
while (queue.isEmpty()) {
System.out.println("缓冲区空,消费者等待...");
wait(); // 释放锁,进入 Wait Set
}
int item = queue.poll();
System.out.println("消费: " + item);
notifyAll(); // 唤醒所有等待的生产者
return item;
}
}
|
关键细节与常见陷阱
1. 必须用 while 而不是 if
1
2
3
4
5
| // ❌ 错误:if 只判断一次,可能存在虚假唤醒
if (!condition) { lock.wait(); }
// ✅ 正确:循环检查条件
while (!condition) { lock.wait(); }
|
原因:线程被唤醒后重新竞争锁,在竞争期间条件可能被其他线程改变(虚假唤醒)。
2. notify vs notifyAll
1
2
| notify() → 只唤醒1个 → 如果唤错线程,可能死锁
notifyAll() → 唤醒所有 → 更安全,性能略低
|
一般优先使用 notifyAll(),除非你能确保所有等待线程等待同一个条件。
3. wait() 会释放锁,sleep() 不会
1
2
3
4
5
| // wait:释放锁,让其他线程进入
synchronized(obj) { obj.wait(); } // ✅ 释放 obj 的锁
// sleep:持锁睡觉,其他线程无法进入
synchronized(obj) { Thread.sleep(1000); } // ❌ 仍然持有锁
|
底层实现(JVM 层)
1
2
3
4
| Java 层 JVM 层 (C++) OS 层
───────── ────────────── ──────
object.wait() → ObjectMonitor::wait() → pthread_cond_wait()
object.notify() → ObjectMonitor::notify() → pthread_cond_signal()
|
- HotSpot JVM 中由
ObjectMonitor 类实现,内部维护 _WaitSet(等待集)和 _EntryList(竞争队列) - 最终依赖操作系统的**条件变量(condition variable)**机制
与 java.util.concurrent 的对比
| wait/notify | Condition(Lock配套) |
|---|
| 绑定 | Object 对象 | 显式 Lock |
| 等待队列 | 一个 | 可多个(更精细控制) |
| 中断响应 | 支持 | 支持 |
| 超时等待 | wait(timeout) | await(timeout, unit) |
| 推荐场景 | 简单同步 | 复杂并发控制 |
现代 Java 开发中推荐使用 ReentrantLock + Condition,但理解 wait/notify 是掌握 Java 并发的基础。