一、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()
唤醒后需重新竞争锁。