wait 和 notify 为什么要放在 synchronized 代码块中?
wait() 和 notify() / notifyAll() 是 Java 中用于线程间通信的方法,它们必须在 synchronized 代码块或方法中使用,原因主要有以下几点:
监视器规则
Java 规定,wait() 和 notify() 必须在拥有对象监视器的线程中调用。换句话说,调用这些方法的线程必须首先获得对象的锁(通过 synchronized)。
如果线程没有持有对象的锁,调用 wait() 将会抛出 IllegalMonitorStateException 异常,因此,必须在同步代码块中调用这些方法,以确保当前线程持有正确的锁
线程安全
wait() 和 notify() 必然是成对出现的,如果一个线程被 wait() 方法阻塞,那么必然需要通过 notify() 方法来唤醒这个被阻塞的线程,从而实现多线程之间的通信。
因此需要保证 wait() 和 notify() 操作需要保证原子性,即在同一时刻只能有一个线程执行这些操作。synchronized 关键字确保了这一点,因为它会锁定对象或类的监视器,防止多个线程同时进入临界区。
如果不在同步代码块中,多个线程可能会同时调用 wait() 或 notify(),导致不可预测的行为和数据不一致。
避免 lost wake up 问题
Java 强制要求 wait() 和 notify() 必须在同步块中调用,以避免 lost wake up 问题。这种问题发生在多个线程竞争同一个锁时,可能会导致某些线程的唤醒信号丢失。
锁的管理:
wait() 和 notify() 方法需要操作对象的监视器锁(monitor lock)。
当一个线程调用 wait() 方法时,它会释放当前持有的监视器锁,并进入等待状态,直到其他线程调用 notify() 或 notifyAll() 方法来唤醒它。
同样,notify() 和 notifyAll() 方法也会释放监视器锁,以便等待的线程可以重新获取锁并继续执行。
因此,这些方法必须在同步代码块中调用,以确保线程能够正确地获取和释放锁。
示例代码
public class Example {
private final Object lock = new Object();
public void doSomething() {
synchronized (lock) {
try {
while (/* condition not met */) {
lock.wait(); // 等待条件满足
}
// 执行相关操作
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
public void notifySomething() {
synchronized (lock) {
// 修改条件
lock.notifyAll(); // 通知等待的线程
}
}
}
总结
将 wait() 和 notify() 放在 synchronized 代码块中是为了确保线程安全、遵循监视器规则、避免死锁。