使用 wait、notify、notifyAll 方法时需要注意哪些问题?

参考回答**

在使用 waitnotifynotifyAll 方法时,需要注意以下问题:

  1. 只能在同步代码块或同步方法中使用
    • 这三个方法必须在获取了对象锁的前提下调用,否则会抛出 IllegalMonitorStateException 异常。
    • 它们必须在 synchronized 修饰的代码块或方法内使用。
  2. 必须配合对象锁使用
    • 这些方法是属于对象实例的,而非线程。调用 waitnotify 时,线程必须持有调用对象的锁。
  3. wait 方法会释放锁
    • 当线程调用 wait 时,会释放当前持有的对象锁,并进入等待状态,直到被 notifynotifyAll 唤醒。
    • 唤醒后,线程需要重新尝试获取锁,才能继续执行。
  4. notifynotifyAll 不释放锁
    • 调用 notifynotifyAll 的线程不会立即释放锁,而是继续执行代码,直到退出同步代码块后才释放锁。
  5. 避免死锁和遗漏唤醒
    • 需要精心设计同步逻辑,避免多个线程之间互相等待而导致死锁。
    • 如果使用 notify 唤醒特定线程,可能会导致某些线程一直处于等待状态。
  6. 推荐使用 while 而非 if 检查条件
    • 在等待条件时,推荐使用 while 循环重新检查条件,而不是 if,以避免虚假唤醒(Spurious Wakeup)。

详细讲解与拓展

1. 使用方法

  • wait 方法:使当前线程进入等待状态,并释放当前持有的锁。
    synchronized (lock) {
      lock.wait(); // 当前线程等待
    }
    
  • notify 方法:唤醒一个在 wait 状态下等待的线程。
    synchronized (lock) {
      lock.notify(); // 唤醒一个线程
    }
    
  • notifyAll 方法:唤醒所有在 wait 状态下等待的线程。
    synchronized (lock) {
      lock.notifyAll(); // 唤醒所有线程
    }
    

2. 示例代码

生产者-消费者模型:

import java.util.LinkedList;
import java.util.Queue;

public class ProducerConsumer {
    private final Queue<Integer> queue = new LinkedList<>();
    private final int CAPACITY = 5;

    public static void main(String[] args) {
        ProducerConsumer pc = new ProducerConsumer();

        Thread producer = new Thread(pc.new Producer());
        Thread consumer = new Thread(pc.new Consumer());

        producer.start();
        consumer.start();
    }

    class Producer implements Runnable {
        @Override
        public void run() {
            int value = 0;
            while (true) {
                synchronized (queue) {
                    while (queue.size() == CAPACITY) {
                        try {
                            System.out.println("Queue is full, producer waiting...");
                            queue.wait(); // 释放锁并等待
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    System.out.println("Producing: " + value);
                    queue.offer(value++);
                    queue.notifyAll(); // 唤醒消费者
                }
            }
        }
    }

    class Consumer implements Runnable {
        @Override
        public void run() {
            while (true) {
                synchronized (queue) {
                    while (queue.isEmpty()) {
                        try {
                            System.out.println("Queue is empty, consumer waiting...");
                            queue.wait(); // 释放锁并等待
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    int value = queue.poll();
                    System.out.println("Consuming: " + value);
                    queue.notifyAll(); // 唤醒生产者
                }
            }
        }
    }
}

3. 常见问题和注意事项

  1. 必须持有锁:
  • 调用 waitnotifynotifyAll 方法时,当前线程必须持有对象锁,否则会抛出 IllegalMonitorStateException

  • 例如:

    “`java
    Object lock = new Object();
    lock.notify(); // 错误:没有持有锁
    “`

  1. 使用 while 检查条件:
  • 避免虚假唤醒时条件判断失败。例如:

    “`java
    synchronized (lock) {
    while (conditionNotMet) { // 用 while 循环而非 if
    lock.wait();
    }
    // 继续执行逻辑
    }
    “`

  1. 避免死锁:
  • 如果线程在进入 wait 后没有合适的线程调用 notifynotifyAll,会导致程序永久阻塞。
  1. notifynotifyAll 的选择:
  • notify 唤醒一个等待线程;notifyAll 唤醒所有等待线程。
  • 推荐在条件复杂时使用 notifyAll,以避免遗漏唤醒的风险。
  1. 配合条件变量使用:
  • 在多线程场景下,可能存在多个条件变量需要分别处理。例如,生产者-消费者模型中可以用两个标志变量分别控制队列的空满状态。

4. 拓展知识

  1. 虚假唤醒(Spurious Wakeup)
  • 虚假唤醒指线程在没有明确 notifynotifyAll 调用的情况下被唤醒,这在多处理器环境中可能发生。
  • 为避免问题,必须在 wait 返回后重新检查条件。
  1. wait 的超时版本
  • wait方法支持超时参数,避免线程无限期等待:

    “`java
    synchronized (lock) {
    lock.wait(1000); // 最多等待 1 秒
    }
    “`

  1. 推荐使用高级并发工具
  • 虽然 waitnotify 是线程间通信的基础,但更推荐使用 Java 的并发工具类(如 ConditionSemaphoreBlockingQueue),它们封装了更高级的同步机制,易于使用且更安全。

总结

  • 使用 waitnotifynotifyAll 时必须确保在同步代码块内调用,并小心处理线程间的通信和状态条件。
  • 推荐使用 while 循环检查条件,以避免虚假唤醒带来的问题。
  • 对于复杂场景,可以考虑使用更高级的并发工具类,如 ConditionBlockingQueue,以提高代码的可读性和可靠性。

发表评论

后才能评论