跳转至

Java 中的 Wait 和 Notify:线程间通信的关键机制

简介

在多线程编程中,线程间的有效通信至关重要。Java 提供了 wait()notify() 方法,它们是实现线程间同步与通信的基础工具。通过这两个方法,线程可以暂停执行并等待特定条件满足,同时其他线程能够在条件达成时通知等待的线程继续执行。本文将深入探讨 wait()notify() 的概念、使用方法、常见实践以及最佳实践,帮助读者更好地掌握这一重要的多线程编程技术。

目录

  1. 基础概念
    • wait() 方法
    • notify() 方法
    • notifyAll() 方法
  2. 使用方法
    • 调用 wait()
    • 调用 notify()notifyAll()
    • 示例代码
  3. 常见实践
    • 生产者 - 消费者模式
    • 资源池管理
  4. 最佳实践
    • 避免死锁
    • 使用 while 循环检查条件
    • 合理选择 notify()notifyAll()
  5. 小结
  6. 参考资料

基础概念

wait() 方法

wait()Object 类的实例方法。当一个线程调用对象的 wait() 方法时,该线程会释放对象的锁,并进入等待状态。直到其他线程调用该对象的 notify()notifyAll() 方法,或者等待时间超时(如果使用了带超时参数的 wait() 方法),该线程才会被唤醒并重新尝试获取对象的锁,然后继续执行。

notify() 方法

notify() 同样是 Object 类的实例方法。当一个线程调用对象的 notify() 方法时,它会随机唤醒在此对象监视器(锁)上等待的单个线程。被唤醒的线程将进入可运行状态,等待获取对象的锁,一旦获取到锁,就会从 wait() 方法调用处继续执行。

notifyAll() 方法

notifyAll() 也是 Object 类的实例方法。与 notify() 不同,notifyAll() 会唤醒在此对象监视器(锁)上等待的所有线程。这些被唤醒的线程都会竞争获取对象的锁,获取到锁的线程将从 wait() 方法调用处继续执行。

使用方法

调用 wait()

要调用 wait() 方法,必须在同步块(synchronized block)或同步方法(synchronized method)中进行,因为 wait() 方法会释放对象的锁。示例如下:

public class WaitExample {
    public static void main(String[] args) {
        Object lock = new Object();
        Thread waitingThread = new Thread(() -> {
            synchronized (lock) {
                try {
                    System.out.println("等待线程开始等待...");
                    lock.wait();
                    System.out.println("等待线程被唤醒...");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        waitingThread.start();

        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        Thread notifyingThread = new Thread(() -> {
            synchronized (lock) {
                System.out.println("通知线程开始通知...");
                lock.notify();
            }
        });

        notifyingThread.start();
    }
}

调用 notify()notifyAll()

notify()notifyAll() 方法也必须在同步块或同步方法中调用,因为它们需要获取对象的锁才能操作等待队列中的线程。在上述示例中,notifyingThread 调用了 lock.notify() 来唤醒等待线程。

示例代码

下面是一个完整的生产者 - 消费者示例,展示了 wait()notify() 的实际应用:

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

class Producer implements Runnable {
    private final Queue<Integer> queue;
    private final int capacity;

    public Producer(Queue<Integer> queue, int capacity) {
        this.queue = queue;
        this.capacity = capacity;
    }

    @Override
    public void run() {
        int value = 0;
        while (true) {
            synchronized (queue) {
                while (queue.size() == capacity) {
                    try {
                        System.out.println("队列已满,生产者等待...");
                        queue.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                queue.add(value++);
                System.out.println("生产者生产: " + value);
                queue.notify();
            }
        }
    }
}

class Consumer implements Runnable {
    private final Queue<Integer> queue;

    public Consumer(Queue<Integer> queue) {
        this.queue = queue;
    }

    @Override
    public void run() {
        while (true) {
            synchronized (queue) {
                while (queue.isEmpty()) {
                    try {
                        System.out.println("队列为空,消费者等待...");
                        queue.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                int consumed = queue.poll();
                System.out.println("消费者消费: " + consumed);
                queue.notify();
            }
        }
    }
}

public class ProducerConsumerExample {
    public static void main(String[] args) {
        Queue<Integer> queue = new LinkedList<>();
        int capacity = 5;

        Thread producerThread = new Thread(new Producer(queue, capacity));
        Thread consumerThread = new Thread(new Consumer(queue));

        producerThread.start();
        consumerThread.start();
    }
}

常见实践

生产者 - 消费者模式

在生产者 - 消费者模式中,生产者线程生成数据并将其放入共享队列,消费者线程从队列中取出数据进行处理。wait()notify() 方法用于协调生产者和消费者之间的同步,确保队列在满时生产者等待,队列为空时消费者等待。上述示例代码就是一个典型的生产者 - 消费者模式实现。

资源池管理

在资源池管理中,资源池维护一定数量的资源,多个线程需要获取和释放这些资源。当资源池为空时,请求资源的线程可以调用 wait() 方法等待资源可用;当有线程释放资源时,调用 notify()notifyAll() 方法唤醒等待的线程。

最佳实践

避免死锁

死锁是多线程编程中常见的问题,使用 wait()notify() 时需要特别注意。确保线程获取锁和释放锁的顺序一致,避免循环依赖导致死锁。例如,在多个线程对多个对象进行加锁操作时,统一按照某种固定顺序获取锁。

使用 while 循环检查条件

在调用 wait() 方法时,应该使用 while 循环检查等待条件,而不是 if 语句。这是因为线程被唤醒后可能不是因为等待的条件满足,而是被虚假唤醒。使用 while 循环可以确保线程在条件真正满足时才继续执行。

synchronized (lock) {
    while (!condition) {
        try {
            lock.wait();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    // 执行相应操作
}

合理选择 notify()notifyAll()

如果只有一个线程等待特定条件,使用 notify() 方法可以提高效率,因为它只唤醒一个线程。但如果有多个线程等待不同条件,或者不确定具体等待的线程数量,使用 notifyAll() 方法确保所有等待线程都有机会被唤醒并检查条件。

小结

wait()notify() 是 Java 多线程编程中实现线程间通信的重要方法。通过合理使用这两个方法,我们可以实现复杂的多线程同步逻辑,如生产者 - 消费者模式和资源池管理。在使用过程中,遵循最佳实践,如避免死锁、使用 while 循环检查条件以及合理选择唤醒方法,能够提高程序的稳定性和性能。掌握 wait()notify() 的使用是成为一名优秀 Java 多线程开发者的关键一步。

参考资料