请解释 wait、notify、notifyAll 方法的用途及工作原理。
参考回答
wait()
、notify()
和 notifyAll()
是 Java 中多线程同步的重要方法,它们来自 Object
类,用于实现线程间的通信和协调。它们的主要用途和工作原理如下:
wait()
- 让线程进入 等待状态,并 释放锁,直到其他线程调用
notify()
或notifyAll()
唤醒它。 - 必须在 同步代码块 或 同步方法 中使用,否则会抛出
IllegalMonitorStateException
。
- 让线程进入 等待状态,并 释放锁,直到其他线程调用
notify()
- 唤醒一个正在等待同一锁对象的线程(如果有多个等待线程,则随机唤醒一个)。
- 被唤醒的线程不会立即获得锁,而是需要等待当前线程退出同步块并释放锁。
notifyAll()
- 唤醒所有正在等待同一锁对象的线程。
- 被唤醒的线程会按竞争机制依次获得锁并继续执行。
详细讲解与拓展
1. wait()
的用途及原理
用途:
- 让当前线程暂停执行,直到被其他线程显式唤醒。
- 用于线程间通信,例如生产者-消费者模型中,消费者需要等待生产者提供数据。
工作原理:
- 调用
wait()
的线程会立即进入 等待队列(waiting queue)并释放锁。 - 必须在同步代码块中调用,因为线程需要先获得锁,才能进入等待状态。
示例:
2. notify()
的用途及原理
用途:
- 唤醒一个在当前锁对象上调用
wait()
方法的线程。
工作原理:
- 被唤醒的线程会从等待队列中移到 锁池(entry set)。
- 被唤醒的线程不会立即执行,必须等当前线程释放锁后,才能获得锁并继续执行。
示例:
3. notifyAll()
的用途及原理
用途:
- 唤醒所有在当前锁对象上调用
wait()
方法的线程。
工作原理:
- 被唤醒的线程会从等待队列中移到 锁池。
- 唤醒的线程依次尝试获得锁,只有一个线程能成功进入同步块。
示例:
4. 三者的比较
方法 | 用途 | 释放锁 | 唤醒机制 | 适用场景 |
---|---|---|---|---|
wait() |
线程进入等待状态,直到被唤醒 | 是 | 被 notify() 或 notifyAll() 唤醒 |
等待其他线程完成某些条件时暂停自身执行 |
notify() |
唤醒一个等待该锁的线程 | 否 | 随机唤醒一个线程 | 仅需唤醒一个线程 |
notifyAll() |
唤醒所有等待该锁的线程 | 否 | 唤醒所有线程 | 需要唤醒多个线程 |
5. 扩展:等待队列与锁池
- 等待队列(Waiting Queue):
- 调用
wait()
的线程会进入等待队列。 - 等待队列中的线程处于 等待状态,直到被
notify()
或notifyAll()
唤醒。
- 调用
- 锁池(Entry Set):
- 被
notify()
或notifyAll()
唤醒的线程会进入锁池。 - 锁池中的线程处于 阻塞状态,等待获取锁。
- 被
执行顺序:
- 线程调用
wait()
-> 进入等待队列。 - 其他线程调用
notify()
-> 唤醒线程移到锁池。 - 当前线程退出同步块并释放锁 -> 锁池中的线程竞争锁,成功的线程继续执行。
6. 常见问题与注意事项
- 必须在同步块中使用:
wait()
、notify()
和notifyAll()
必须在synchronized
块或方法中调用,否则会抛出IllegalMonitorStateException
。
- 可能出现线程饥饿问题:
- 如果只使用
notify()
,可能导致某些线程长时间无法被唤醒。使用notifyAll()
可以避免这一问题。
- 如果只使用
- 多线程通信需要额外条件判断:
- 线程被唤醒后不一定满足运行条件,通常需要结合条件变量进行判断。
示例:条件判断
总结
wait()
、notify()
和notifyAll()
是多线程通信的核心工具,用于协调线程间的执行顺序。wait()
让线程进入等待队列,并释放锁;notify()
唤醒一个线程;notifyAll()
唤醒所有等待线程。- 必须在同步代码块中使用,并配合条件变量来保证程序逻辑的正确性。