请解释 wait、notify、notifyAll 方法的用途及工作原理。

参考回答

wait()notify()notifyAll() 是 Java 中多线程同步的重要方法,它们来自 Object,用于实现线程间的通信和协调。它们的主要用途和工作原理如下:

  1. wait()
    • 让线程进入 等待状态,并 释放锁,直到其他线程调用 notify()notifyAll() 唤醒它。
    • 必须在 同步代码块同步方法 中使用,否则会抛出 IllegalMonitorStateException
  2. notify()
    • 唤醒一个正在等待同一锁对象的线程(如果有多个等待线程,则随机唤醒一个)。
    • 被唤醒的线程不会立即获得锁,而是需要等待当前线程退出同步块并释放锁。
  3. 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() 唤醒的线程会进入锁池。
    • 锁池中的线程处于 阻塞状态,等待获取锁。

执行顺序

  1. 线程调用 wait() -> 进入等待队列。
  2. 其他线程调用 notify() -> 唤醒线程移到锁池。
  3. 当前线程退出同步块并释放锁 -> 锁池中的线程竞争锁,成功的线程继续执行。

6. 常见问题与注意事项

  1. 必须在同步块中使用
    • wait()notify()notifyAll() 必须在 synchronized 块或方法中调用,否则会抛出 IllegalMonitorStateException
  2. 可能出现线程饥饿问题
    • 如果只使用 notify(),可能导致某些线程长时间无法被唤醒。使用 notifyAll() 可以避免这一问题。
  3. 多线程通信需要额外条件判断
    • 线程被唤醒后不一定满足运行条件,通常需要结合条件变量进行判断。

示例:条件判断

synchronized (lock) {
    while (!condition) { // 必须使用 while 避免虚假唤醒
        lock.wait();
    }
    // 处理满足条件的逻辑
}

总结

  • wait()notify()notifyAll() 是多线程通信的核心工具,用于协调线程间的执行顺序。
  • wait() 让线程进入等待队列,并释放锁;notify() 唤醒一个线程;notifyAll() 唤醒所有等待线程。
  • 必须在同步代码块中使用,并配合条件变量来保证程序逻辑的正确性。

发表评论

后才能评论