请解释 wait、notify、notifyAll 方法的用途及工作原理。
参考回答
wait()
、notify()
和 notifyAll()
是 Java 中多线程同步的重要方法,它们来自 Object
类,用于实现线程间的通信和协调。它们的主要用途和工作原理如下:
wait()
- 让线程进入 等待状态,并 释放锁,直到其他线程调用
notify()
或notifyAll()
唤醒它。 - 必须在 同步代码块 或 同步方法 中使用,否则会抛出
IllegalMonitorStateException
。
- 让线程进入 等待状态,并 释放锁,直到其他线程调用
notify()
- 唤醒一个正在等待同一锁对象的线程(如果有多个等待线程,则随机唤醒一个)。
- 被唤醒的线程不会立即获得锁,而是需要等待当前线程退出同步块并释放锁。
notifyAll()
- 唤醒所有正在等待同一锁对象的线程。
- 被唤醒的线程会按竞争机制依次获得锁并继续执行。
详细讲解与拓展
1. wait()
的用途及原理
用途:
- 让当前线程暂停执行,直到被其他线程显式唤醒。
- 用于线程间通信,例如生产者-消费者模型中,消费者需要等待生产者提供数据。
工作原理:
- 调用
wait()
的线程会立即进入 等待队列(waiting queue)并释放锁。 - 必须在同步代码块中调用,因为线程需要先获得锁,才能进入等待状态。
示例:
public class WaitExample {
private static final Object lock = new Object();
public static void main(String[] args) {
Thread thread = new Thread(() -> {
synchronized (lock) {
try {
System.out.println("Thread waiting...");
lock.wait(); // 进入等待状态
System.out.println("Thread resumed");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
thread.start();
try {
Thread.sleep(1000); // 主线程暂停1秒
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lock) {
lock.notify(); // 唤醒等待的线程
System.out.println("Thread notified");
}
}
}
2. notify()
的用途及原理
用途:
- 唤醒一个在当前锁对象上调用
wait()
方法的线程。
工作原理:
- 被唤醒的线程会从等待队列中移到 锁池(entry set)。
- 被唤醒的线程不会立即执行,必须等当前线程释放锁后,才能获得锁并继续执行。
示例:
public class NotifyExample {
private static final Object lock = new Object();
public static void main(String[] args) {
Thread waitingThread = new Thread(() -> {
synchronized (lock) {
try {
System.out.println("Thread waiting...");
lock.wait(); // 等待被唤醒
System.out.println("Thread resumed");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
waitingThread.start();
Thread notifyingThread = new Thread(() -> {
synchronized (lock) {
System.out.println("Thread notifying...");
lock.notify(); // 唤醒等待的线程
}
});
try {
Thread.sleep(1000); // 确保 waitingThread 先进入等待
} catch (InterruptedException e) {
e.printStackTrace();
}
notifyingThread.start();
}
}
3. notifyAll()
的用途及原理
用途:
- 唤醒所有在当前锁对象上调用
wait()
方法的线程。
工作原理:
- 被唤醒的线程会从等待队列中移到 锁池。
- 唤醒的线程依次尝试获得锁,只有一个线程能成功进入同步块。
示例:
public class NotifyAllExample {
private static final Object lock = new Object();
public static void main(String[] args) {
for (int i = 0; i < 3; i++) {
Thread thread = new Thread(() -> {
synchronized (lock) {
try {
System.out.println(Thread.currentThread().getName() + " waiting...");
lock.wait(); // 进入等待状态
System.out.println(Thread.currentThread().getName() + " resumed");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
thread.start();
}
try {
Thread.sleep(1000); // 主线程暂停1秒
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lock) {
System.out.println("Notify all threads...");
lock.notifyAll(); // 唤醒所有等待线程
}
}
}
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()
可以避免这一问题。
- 如果只使用
- 多线程通信需要额外条件判断:
- 线程被唤醒后不一定满足运行条件,通常需要结合条件变量进行判断。
示例:条件判断
synchronized (lock) {
while (!condition) { // 必须使用 while 避免虚假唤醒
lock.wait();
}
// 处理满足条件的逻辑
}
总结
wait()
、notify()
和notifyAll()
是多线程通信的核心工具,用于协调线程间的执行顺序。wait()
让线程进入等待队列,并释放锁;notify()
唤醒一个线程;notifyAll()
唤醒所有等待线程。- 必须在同步代码块中使用,并配合条件变量来保证程序逻辑的正确性。