跳转至

Java 中的 wait() 方法:深入解析与实践

简介

在 Java 多线程编程中,wait() 方法是一个强大且常用的工具,用于协调多个线程之间的执行顺序和共享资源的访问。理解并正确使用 wait() 方法对于编写高效、健壮的多线程应用程序至关重要。本文将深入探讨 wait() 方法的基础概念、使用方法、常见实践以及最佳实践,帮助读者全面掌握这一关键技术点。

目录

  1. 基础概念
    • wait() 方法的定义与作用
    • 与对象锁的关系
  2. 使用方法
    • 调用 wait() 方法
    • wait(long timeout)wait(long timeout, int nanos) 重载方法
  3. 常见实践
    • 生产者 - 消费者模型
    • 线程间的同步与通信
  4. 最佳实践
    • 正确处理 InterruptedException
    • 使用 notifyAll()notify() 的时机
  5. 小结
  6. 参考资料

基础概念

wait() 方法的定义与作用

wait() 方法是 java.lang.Object 类的成员方法,它的作用是使当前线程等待,直到其他线程调用该对象的 notify()notifyAll() 方法。这一机制提供了一种线程间的通信方式,允许线程在特定条件满足之前暂停执行,从而避免资源竞争和不必要的循环检查。

与对象锁的关系

wait() 方法必须在同步代码块或同步方法中调用,这是因为调用 wait() 方法时,当前线程会释放对象的锁。当其他线程调用 notify()notifyAll() 方法唤醒等待线程时,等待线程会重新获取对象的锁,然后继续执行。这种机制确保了在多线程环境下,对共享资源的访问是安全和有序的。

使用方法

调用 wait() 方法

以下是一个简单的示例,展示了如何调用 wait() 方法:

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

        Thread waitingThread = new Thread(() -> {
            synchronized (lock) {
                System.out.println("等待线程开始等待...");
                try {
                    lock.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("等待线程被唤醒...");
            }
        });

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

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

在这个示例中,waitingThread 在获取 lock 对象的锁后调用 lock.wait(),此时该线程会释放 lock 的锁并进入等待状态。notifyingThread 在获取 lock 对象的锁后调用 lock.notify(),唤醒 waitingThreadwaitingThread 重新获取锁后继续执行。

wait(long timeout)wait(long timeout, int nanos) 重载方法

wait(long timeout) 方法允许指定等待的最长时间(以毫秒为单位)。如果在指定时间内没有其他线程调用 notify()notifyAll() 方法,等待线程会自动唤醒。wait(long timeout, int nanos) 方法则更加精确,允许指定等待的时间(以毫秒和纳秒为单位)。

public class WaitTimeoutExample {
    public static void main(String[] args) {
        Object lock = new Object();

        Thread waitingThread = new Thread(() -> {
            synchronized (lock) {
                System.out.println("等待线程开始等待...");
                try {
                    lock.wait(2000); // 等待 2 秒
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("等待线程被唤醒或超时...");
            }
        });

        waitingThread.start();
    }
}

在这个示例中,waitingThread 调用 lock.wait(2000),最多等待 2 秒。如果 2 秒内没有其他线程唤醒它,它会自动唤醒并继续执行。

常见实践

生产者 - 消费者模型

生产者 - 消费者模型是 wait() 方法的经典应用场景。在该模型中,生产者线程生成数据并放入共享缓冲区,消费者线程从缓冲区中取出数据进行处理。为了避免缓冲区溢出或下溢,需要使用 wait()notify() 方法进行线程间的同步。

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

class Buffer {
    private final int capacity;
    private final Queue<Integer> queue = new LinkedList<>();

    public Buffer(int capacity) {
        this.capacity = capacity;
    }

    public synchronized void produce(int item) throws InterruptedException {
        while (queue.size() == capacity) {
            wait(); // 缓冲区已满,等待消费者消费
        }
        queue.add(item);
        System.out.println("生产: " + item);
        notifyAll(); // 通知消费者有新数据
    }

    public synchronized int consume() throws InterruptedException {
        while (queue.isEmpty()) {
            wait(); // 缓冲区为空,等待生产者生产
        }
        int item = queue.poll();
        System.out.println("消费: " + item);
        notifyAll(); // 通知生产者有空间可生产
        return item;
    }
}

class Producer implements Runnable {
    private final Buffer buffer;

    public Producer(Buffer buffer) {
        this.buffer = buffer;
    }

    @Override
    public void run() {
        for (int i = 1; i <= 10; i++) {
            try {
                buffer.produce(i);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

class Consumer implements Runnable {
    private final Buffer buffer;

    public Consumer(Buffer buffer) {
        this.buffer = buffer;
    }

    @Override
    public void run() {
        for (int i = 1; i <= 10; i++) {
            try {
                buffer.consume();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

public class ProducerConsumerExample {
    public static void main(String[] args) {
        Buffer buffer = new Buffer(5);
        Thread producerThread = new Thread(new Producer(buffer));
        Thread consumerThread = new Thread(new Consumer(buffer));

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

在这个示例中,Buffer 类使用 wait()notifyAll() 方法来协调生产者和消费者线程的行为。生产者线程在缓冲区满时等待,消费者线程在缓冲区空时等待,从而保证了数据的正确处理。

线程间的同步与通信

除了生产者 - 消费者模型,wait() 方法还可用于其他线程间的同步与通信场景。例如,一个线程需要等待另一个线程完成某项任务后再继续执行。

public class ThreadCommunicationExample {
    private static boolean taskCompleted = false;

    public static void main(String[] args) {
        Object lock = new Object();

        Thread taskThread = new Thread(() -> {
            synchronized (lock) {
                System.out.println("任务线程开始执行...");
                // 模拟任务执行
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                taskCompleted = true;
                System.out.println("任务线程完成任务...");
                lock.notify();
            }
        });

        Thread waitingThread = new Thread(() -> {
            synchronized (lock) {
                System.out.println("等待线程开始等待...");
                while (!taskCompleted) {
                    try {
                        lock.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println("等待线程任务已完成...");
            }
        });

        taskThread.start();
        waitingThread.start();
    }
}

在这个示例中,waitingThread 等待 taskThread 完成任务。taskThread 完成任务后设置 taskCompletedtrue 并调用 lock.notify() 唤醒 waitingThread

最佳实践

正确处理 InterruptedException

wait() 方法会抛出 InterruptedException,因此在调用 wait() 时必须正确处理该异常。通常建议在捕获异常后,清理资源并合理终止线程。

public class InterruptedExceptionHandlingExample {
    public static void main(String[] args) {
        Object lock = new Object();

        Thread waitingThread = new Thread(() -> {
            synchronized (lock) {
                System.out.println("等待线程开始等待...");
                try {
                    lock.wait();
                } catch (InterruptedException e) {
                    System.out.println("等待线程被中断...");
                    // 清理资源
                    // 终止线程
                }
                System.out.println("等待线程被唤醒或中断...");
            }
        });

        waitingThread.start();

        // 主线程中断等待线程
        waitingThread.interrupt();
    }
}

使用 notifyAll()notify() 的时机

notify() 方法唤醒在此对象监视器上等待的单个线程,而 notifyAll() 方法唤醒在此对象监视器上等待的所有线程。通常情况下,如果只有一个线程需要被唤醒,使用 notify() 方法可以提高性能;如果多个线程都需要被唤醒,例如在生产者 - 消费者模型中,使用 notifyAll() 方法更为合适。

public class NotifyVsNotifyAllExample {
    public static void main(String[] args) {
        Object lock = new Object();

        Thread waitingThread1 = new Thread(() -> {
            synchronized (lock) {
                System.out.println("等待线程 1 开始等待...");
                try {
                    lock.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("等待线程 1 被唤醒...");
            }
        });

        Thread waitingThread2 = new Thread(() -> {
            synchronized (lock) {
                System.out.println("等待线程 2 开始等待...");
                try {
                    lock.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("等待线程 2 被唤醒...");
            }
        });

        Thread notifyingThread = new Thread(() -> {
            synchronized (lock) {
                System.out.println("通知线程开始执行...");
                lock.notify(); // 仅唤醒一个等待线程
                // lock.notifyAll(); // 唤醒所有等待线程
                System.out.println("通知线程已发送通知...");
            }
        });

        waitingThread1.start();
        waitingThread2.start();
        notifyingThread.start();
    }
}

小结

wait() 方法是 Java 多线程编程中的重要工具,用于实现线程间的同步与通信。通过正确理解和使用 wait() 方法,以及相关的 notify()notifyAll() 方法,可以编写高效、健壮的多线程应用程序。在实际应用中,需要注意正确处理 InterruptedException,并根据具体场景选择合适的通知方式(notify()notifyAll())。

参考资料

希望本文能帮助读者深入理解并高效使用 Java 中的 wait() 方法,在多线程编程中更加得心应手。