一、Monitor对象的核心结构
每个Java对象都关联一个Monitor对象,其核心字段包括:
owner:指向当前持有锁的线程。EntryList:存放竞争锁的线程队列(阻塞状态)。WaitSet:存放调用wait()后释放锁的线程队列(等待状态)。
二、wait()和notify()的执行逻辑
1. wait()方法的作用
- 释放锁:当前线程释放对象的Monitor锁。
- 进入等待队列:线程被放入对象的
WaitSet队列中(等待被唤醒)。 - 挂起线程:线程进入
WAITING或TIMED_WAITING状态,直到被notify()唤醒或超时。
2. notify()方法的作用
- 唤醒线程:从
WaitSet中随机唤醒一个线程,将其移动到EntryList队列中(重新竞争锁)。 - 不释放锁:调用
notify()的线程不会立即释放锁,需退出同步块后锁才释放。
3. notifyAll()方法的作用
- 唤醒
WaitSet中所有等待线程,将它们全部移动到EntryList。
三、关键规则与注意事项
-
必须在同步块内调用
wait()/notify()必须持有对象的Monitor锁,否则抛出IllegalMonitorStateException。 -
条件检查必须用
while而非if
避免虚假唤醒(Spurious Wakeup),即使未被通知,线程也可能从wait()返回。例如:synchronized (lock) { while (条件不满足) { // 必须用while循环检查条件 lock.wait(); } // 执行操作 } -
唤醒策略
notify():随机唤醒一个线程,适合单生产者-单消费者场景。notifyAll():唤醒所有线程,确保不会遗漏等待线程(更安全,但可能增加竞争)。
-
锁释放与重入
wait()会释放锁,唤醒后需重新竞争锁才能继续执行。notify()不会释放锁,线程需退出同步块才会释放锁。
四、执行流程图解
生产者线程:
1. 获取锁 → 检查条件(消息是否已消费)
↓ 条件满足
2. 生产消息 → 设置empty=false → 调用notifyAll()
3. 退出同步块 → 释放锁
消费者线程:
1. 获取锁 → 检查条件(消息是否未消费)
↓ 条件满足
2. 消费消息 → 设置empty=true → 调用notifyAll()
3. 退出同步块 → 释放锁
等待线程(WaitSet):
1. 被notifyAll()唤醒 → 移动到EntryList
2. 重新竞争锁 → 获取锁后继续执行
六、常见问题
1. 为什么wait()和notify()属于Object类?
- 因为它们依赖对象的Monitor锁,而每个对象都可作为锁载体。
2. 与sleep()的区别?
sleep():不释放锁,线程暂停指定时间。wait():释放锁,线程进入等待状态直到被唤醒。
3. 如何避免死锁?
- 确保所有
wait()都有对应的notify()唤醒。 - 避免嵌套锁或长时间持有锁。
七、总结
通过wait()/notify()配合Monitor锁,可以实现线程间的精准协作:
wait():释放锁并等待条件满足。notify():通知等待线程条件已变化。
正确使用这两个方法需严格遵循同步规则和条件检查,避免线程饥饿或死锁问题。
拓展:wait与sleep
1. 所属类与调用方式
| 方法 | 所属类 | 调用方式 |
|---|---|---|
wait() | Object | 必须在同步块中调用(需持有对象锁) |
sleep() | Thread | 静态方法,可直接调用(无需持有锁) |
2. 锁的释放行为
| 方法 | 是否释放锁? | 锁释放时机 |
|---|---|---|
wait() | ✅ 释放锁 | 调用wait()后立即释放锁,线程进入等待状态 |
sleep() | ❌ 不释放锁 | 线程休眠期间一直持有锁,其他线程无法进入同步块 |
3. 唤醒机制
| 方法 | 唤醒方式 | 中断响应 |
|---|---|---|
wait() | 需其他线程调用notify()/notifyAll() | 响应中断(抛出InterruptedException) |
sleep() | 超时后自动唤醒,或通过interrupt()中断 | 响应中断(抛出InterruptedException) |
4. 关键区别总结
| 特性 | wait() | sleep() |
|---|---|---|
| 锁释放 | 立即释放锁 | 不释放锁 |
| 调用条件 | 必须在同步块中调用 | 任意位置调用 |
| 唤醒方式 | 依赖notify()/notifyAll() | 超时或中断唤醒 |
| 用途 | 线程间协作(依赖共享资源条件) | 单纯暂停线程执行 |
5. 常见误区
-
在非同步块中调用
wait()
会导致IllegalMonitorStateException。 -
用
sleep()代替wait()
可能导致死锁(线程休眠期间不释放锁,其他线程无法获取锁)。 -
虚假唤醒(Spurious Wakeup)
wait()返回后必须用while循环重新检查条件,而非if。 -
中断处理
两者均需捕获InterruptedException,但wait()唤醒后需重新竞争锁。
431

被折叠的 条评论
为什么被折叠?



