Java wait
方法深度解析
简介
在 Java 多线程编程中,wait
方法是一个非常重要的工具,它允许线程在特定条件下暂停执行,释放对象的锁,直到其他线程通知它继续执行。本文将全面介绍 Java wait
方法的基础概念、使用方法、常见实践以及最佳实践,帮助读者深入理解并高效使用该方法。
目录
- 基础概念
- 使用方法
- 常见实践
- 最佳实践
- 小结
- 参考资料
基础概念
wait
是 Object
类的一个实例方法,这意味着 Java 中的任何对象都可以调用该方法。当一个线程调用对象的 wait
方法时,它会释放该对象的锁,并进入该对象的等待池(wait set),处于等待状态。直到其他线程调用该对象的 notify
或 notifyAll
方法,等待池中的线程才会被唤醒。
notify
和 notifyAll
的区别
notify
:随机唤醒等待池中的一个线程。notifyAll
:唤醒等待池中的所有线程,这些线程会竞争对象的锁,获得锁的线程才能继续执行。
使用方法
wait
方法有三种重载形式:
- wait()
:线程无限期等待,直到其他线程调用该对象的 notify
或 notifyAll
方法。
- 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();
}
}
代码解释
SharedObject
类中定义了waitMethod
和notifyMethod
方法,这两个方法都使用了synchronized
关键字,确保在同一时间只有一个线程可以访问该对象的这些方法。waitMethod
方法中调用了wait()
方法,使当前线程进入等待状态。notifyMethod
方法中调用了notify()
方法,唤醒等待池中的一个线程。- 在
main
方法中,创建了两个线程:waitingThread
和notifyingThread
,分别调用waitMethod
和notifyMethod
方法。
常见实践
生产者 - 消费者模式
生产者 - 消费者模式是多线程编程中一个经典的问题,wait
和 notify
方法可以很好地解决这个问题。
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();
}
}
代码解释
ProducerConsumerExample
类中定义了一个队列queue
,用于存储生产者生产的数据。- 生产者线程在队列已满时调用
queue.wait()
方法,进入等待状态;生产数据后调用queue.notifyAll()
方法,唤醒消费者线程。 - 消费者线程在队列为空时调用
queue.wait()
方法,进入等待状态;消费数据后调用queue.notifyAll()
方法,唤醒生产者线程。
最佳实践
使用 while
循环检查条件
在调用 wait
方法时,应该使用 while
循环检查条件,而不是 if
语句。这是因为线程被唤醒后,可能会出现虚假唤醒(spurious wakeup)的情况,即线程在没有被 notify
或 notifyAll
唤醒的情况下自行唤醒。使用 while
循环可以确保线程在唤醒后再次检查条件,避免出现错误。
尽量使用 notifyAll
在大多数情况下,建议使用 notifyAll
方法而不是 notify
方法。因为 notify
方法只会随机唤醒一个线程,可能会导致某些线程一直处于等待状态,而 notifyAll
方法可以唤醒所有等待的线程,避免出现线程饥饿的问题。
小结
本文详细介绍了 Java wait
方法的基础概念、使用方法、常见实践以及最佳实践。wait
方法是 Java 多线程编程中一个非常重要的工具,通过合理使用 wait
、notify
和 notifyAll
方法,可以实现线程之间的协作和同步。在使用 wait
方法时,需要注意使用 synchronized
关键字确保线程安全,使用 while
循环检查条件,以及尽量使用 notifyAll
方法。
参考资料
- 《Effective Java》
- 《Java 核心技术》