跳转至

Java wait 方法深度解析

简介

在 Java 多线程编程中,wait 方法是一个非常重要的工具,它允许线程在特定条件下暂停执行,释放对象的锁,直到其他线程通知它继续执行。本文将全面介绍 Java wait 方法的基础概念、使用方法、常见实践以及最佳实践,帮助读者深入理解并高效使用该方法。

目录

  1. 基础概念
  2. 使用方法
  3. 常见实践
  4. 最佳实践
  5. 小结
  6. 参考资料

基础概念

waitObject 类的一个实例方法,这意味着 Java 中的任何对象都可以调用该方法。当一个线程调用对象的 wait 方法时,它会释放该对象的锁,并进入该对象的等待池(wait set),处于等待状态。直到其他线程调用该对象的 notifynotifyAll 方法,等待池中的线程才会被唤醒。

notifynotifyAll 的区别

  • notify:随机唤醒等待池中的一个线程。
  • notifyAll:唤醒等待池中的所有线程,这些线程会竞争对象的锁,获得锁的线程才能继续执行。

使用方法

wait 方法有三种重载形式: - wait():线程无限期等待,直到其他线程调用该对象的 notifynotifyAll 方法。 - wait(long timeout):线程等待指定的毫秒数,如果在超时时间内没有被唤醒,则自动唤醒。 - wait(long timeout, int nanos):线程等待指定的毫秒数和纳秒数。

代码示例

class SharedObject {
    public synchronized void waitMethod() {
        try {
            System.out.println(Thread.currentThread().getName() + " is waiting.");
            // 线程进入等待状态
            wait();
            System.out.println(Thread.currentThread().getName() + " is awakened.");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public synchronized void notifyMethod() {
        // 唤醒等待池中的一个线程
        notify();
        System.out.println("One thread is notified.");
    }
}

public class WaitExample {
    public static void main(String[] args) {
        SharedObject sharedObject = new SharedObject();

        // 创建等待线程
        Thread waitingThread = new Thread(() -> sharedObject.waitMethod(), "WaitingThread");
        // 创建通知线程
        Thread notifyingThread = new Thread(() -> {
            try {
                // 等待一段时间后再通知
                Thread.sleep(2000);
                sharedObject.notifyMethod();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "NotifyingThread");

        waitingThread.start();
        notifyingThread.start();
    }
}

代码解释

  1. SharedObject 类中定义了 waitMethodnotifyMethod 方法,这两个方法都使用了 synchronized 关键字,确保在同一时间只有一个线程可以访问该对象的这些方法。
  2. waitMethod 方法中调用了 wait() 方法,使当前线程进入等待状态。
  3. notifyMethod 方法中调用了 notify() 方法,唤醒等待池中的一个线程。
  4. main 方法中,创建了两个线程:waitingThreadnotifyingThread,分别调用 waitMethodnotifyMethod 方法。

常见实践

生产者 - 消费者模式

生产者 - 消费者模式是多线程编程中一个经典的问题,waitnotify 方法可以很好地解决这个问题。

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

class ProducerConsumerExample {
    private static final int MAX_SIZE = 5;
    private static Queue<Integer> queue = new LinkedList<>();

    public static void main(String[] args) {
        // 生产者线程
        Thread producer = new Thread(() -> {
            try {
                int value = 0;
                while (true) {
                    synchronized (queue) {
                        while (queue.size() == MAX_SIZE) {
                            // 队列已满,生产者线程等待
                            queue.wait();
                        }
                        queue.add(value++);
                        System.out.println("Produced: " + (value - 1));
                        // 唤醒消费者线程
                        queue.notifyAll();
                    }
                    Thread.sleep(1000);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        // 消费者线程
        Thread consumer = new Thread(() -> {
            try {
                while (true) {
                    synchronized (queue) {
                        while (queue.isEmpty()) {
                            // 队列为空,消费者线程等待
                            queue.wait();
                        }
                        int value = queue.poll();
                        System.out.println("Consumed: " + value);
                        // 唤醒生产者线程
                        queue.notifyAll();
                    }
                    Thread.sleep(1000);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

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

代码解释

  1. ProducerConsumerExample 类中定义了一个队列 queue,用于存储生产者生产的数据。
  2. 生产者线程在队列已满时调用 queue.wait() 方法,进入等待状态;生产数据后调用 queue.notifyAll() 方法,唤醒消费者线程。
  3. 消费者线程在队列为空时调用 queue.wait() 方法,进入等待状态;消费数据后调用 queue.notifyAll() 方法,唤醒生产者线程。

最佳实践

使用 while 循环检查条件

在调用 wait 方法时,应该使用 while 循环检查条件,而不是 if 语句。这是因为线程被唤醒后,可能会出现虚假唤醒(spurious wakeup)的情况,即线程在没有被 notifynotifyAll 唤醒的情况下自行唤醒。使用 while 循环可以确保线程在唤醒后再次检查条件,避免出现错误。

尽量使用 notifyAll

在大多数情况下,建议使用 notifyAll 方法而不是 notify 方法。因为 notify 方法只会随机唤醒一个线程,可能会导致某些线程一直处于等待状态,而 notifyAll 方法可以唤醒所有等待的线程,避免出现线程饥饿的问题。

小结

本文详细介绍了 Java wait 方法的基础概念、使用方法、常见实践以及最佳实践。wait 方法是 Java 多线程编程中一个非常重要的工具,通过合理使用 waitnotifynotifyAll 方法,可以实现线程之间的协作和同步。在使用 wait 方法时,需要注意使用 synchronized 关键字确保线程安全,使用 while 循环检查条件,以及尽量使用 notifyAll 方法。

参考资料

  • 《Effective Java》
  • 《Java 核心技术》