跳转至

深入理解 Java 中的 wait() 和 notify()

简介

在多线程编程的世界里,线程之间的协调与通信至关重要。Java 提供了 wait()notify() 方法,它们是对象级别的机制,用于线程间的交互。通过这两个方法,线程能够暂停执行并等待特定条件满足,然后由其他线程唤醒。这篇博客将详细探讨 wait()notify() 的概念、使用方法、常见实践以及最佳实践,帮助你在多线程编程中更好地运用它们。

目录

  1. 基础概念
    • wait() 方法
    • notify() 方法
    • notifyAll() 方法
  2. 使用方法
    • 调用 wait()
    • 调用 notify()notifyAll()
    • 代码示例
  3. 常见实践
    • 生产者 - 消费者模型
    • 线程间的条件等待
  4. 最佳实践
    • 避免死锁
    • 合理使用 notify()notifyAll()
    • 使用 wait() 的超时机制
  5. 小结
  6. 参考资料

基础概念

wait() 方法

wait()Object 类的实例方法。当一个线程调用对象的 wait() 方法时,该线程会释放对象的锁,并进入等待状态。直到其他线程调用同一个对象的 notify()notifyAll() 方法将其唤醒,或者等待时间超时(如果使用了带超时参数的 wait() 方法)。

notify() 方法

notify() 也是 Object 类的实例方法。当一个线程调用对象的 notify() 方法时,它会唤醒在此对象监视器(锁)上等待的单个线程。如果有多个线程在等待,会随机选择一个被唤醒。被唤醒的线程将竞争对象的锁,获取锁后继续执行。

notifyAll() 方法

notifyAll() 同样是 Object 类的实例方法。它会唤醒在此对象监视器(锁)上等待的所有线程。所有被唤醒的线程都会竞争对象的锁,获取锁后继续执行。

使用方法

调用 wait()

调用 wait() 方法需要在同步块或同步方法中进行,因为调用 wait() 方法时,线程需要释放对象的锁。示例代码如下:

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

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

        synchronized (lock) {
            System.out.println("主线程唤醒等待线程");
            lock.notify();
        }
    }
}

调用 notify()notifyAll()

notify()notifyAll() 方法也需要在同步块或同步方法中调用,因为它们需要获取对象的锁才能操作等待队列中的线程。在上述代码中,主线程通过 lock.notify() 唤醒了等待的线程。如果使用 notifyAll(),则会唤醒所有在 lock 上等待的线程。

代码示例

下面是一个更完整的示例,展示生产者 - 消费者模型中 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 value = queue.poll();
                System.out.println("消费: " + value);
                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() 方法等待;当队列已空时,消费者线程调用 wait() 方法等待。当有新数据放入队列或从队列取出数据时,调用 notify()notifyAll() 方法唤醒等待的线程。

线程间的条件等待

在多线程环境中,线程可能需要等待某个条件满足后才能继续执行。例如,一个线程可能需要等待另一个线程完成某项任务后才能继续。这时可以使用 wait()notify() 方法来实现线程间的条件等待。

最佳实践

避免死锁

死锁是多线程编程中常见的问题。为了避免死锁,要确保所有线程以相同的顺序获取锁,并且在适当的时候释放锁。在使用 wait()notify() 时,要注意唤醒的线程是否能够获取到所需的锁继续执行。

合理使用 notify()notifyAll()

notify() 适用于只需要唤醒一个等待线程的场景,这样可以减少竞争,提高性能。而 notifyAll() 适用于需要唤醒所有等待线程的场景,例如当某个条件发生变化,所有等待线程都需要重新检查条件时。

使用 wait() 的超时机制

wait() 有一个带超时参数的重载方法 wait(long timeout)。使用这个方法可以避免线程无限期等待,提高程序的健壮性。例如:

synchronized (lock) {
    try {
        lock.wait(5000); // 等待 5 秒
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

小结

wait()notify() 是 Java 多线程编程中强大的工具,用于线程间的通信和协调。通过理解它们的基础概念、正确的使用方法、常见实践以及最佳实践,你可以编写出高效、健壮的多线程程序。在实际应用中,要根据具体的需求合理使用这些方法,避免出现死锁等问题。

参考资料

希望这篇博客能帮助你深入理解并高效使用 Java 中的 wait()notify() 方法。如果你有任何问题或建议,欢迎在评论区留言。