wait 和 notify 为什么要放在 synchronized 代码块中?
2024-10-16 09:35 阅读(219)

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 代码块中是为了确保线程安全、遵循监视器规则、避免死锁。