Java 中的 wait()
方法:深入解析与实践
简介
在 Java 多线程编程中,wait()
方法是一个强大且常用的工具,用于协调多个线程之间的执行顺序和共享资源的访问。理解并正确使用 wait()
方法对于编写高效、健壮的多线程应用程序至关重要。本文将深入探讨 wait()
方法的基础概念、使用方法、常见实践以及最佳实践,帮助读者全面掌握这一关键技术点。
目录
- 基础概念
wait()
方法的定义与作用- 与对象锁的关系
- 使用方法
- 调用
wait()
方法 wait(long timeout)
和wait(long timeout, int nanos)
重载方法
- 调用
- 常见实践
- 生产者 - 消费者模型
- 线程间的同步与通信
- 最佳实践
- 正确处理
InterruptedException
- 使用
notifyAll()
与notify()
的时机
- 正确处理
- 小结
- 参考资料
基础概念
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()
,唤醒 waitingThread
,waitingThread
重新获取锁后继续执行。
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
完成任务后设置 taskCompleted
为 true
并调用 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()
)。
参考资料
- Oracle Java 官方文档 - Object 类
- 《Effective Java》 - Joshua Bloch
- 《Java 并发编程实战》 - Brian Goetz
希望本文能帮助读者深入理解并高效使用 Java 中的 wait()
方法,在多线程编程中更加得心应手。