Java 中的 wait 和 notify 机制
简介
在多线程编程中,线程之间的协作是一个关键问题。Java 提供了 wait()
和 notify()
方法来实现线程间的通信与协作。通过这两个方法,线程可以在特定条件下暂停执行(wait
),并在条件满足时被其他线程唤醒(notify
)。这篇博客将深入探讨 wait
和 notify
在 Java 中的使用。
目录
- 基础概念
- 使用方法
wait()
方法notify()
方法notifyAll()
方法
- 常见实践
- 生产者 - 消费者模型
- 最佳实践
- 小结
- 参考资料
基础概念
wait()
、notify()
和 notifyAll()
是 Object
类的方法,这意味着 Java 中的每个对象都有这三个方法。它们主要用于线程间的同步和通信。
wait()
:当一个线程调用对象的wait()
方法时,该线程会释放对象的锁,并进入等待状态。直到其他线程调用该对象的notify()
或notifyAll()
方法将其唤醒。notify()
:唤醒在此对象监视器上等待的单个线程。如果有多个线程在等待,则选择其中一个唤醒。notifyAll()
:唤醒在此对象监视器上等待的所有线程。
使用方法
wait()
方法
wait()
方法有三种重载形式:
- wait()
:使当前线程等待,直到另一个线程调用该对象的 notify()
或 notifyAll()
方法。
- wait(long timeout)
:使当前线程等待,直到另一个线程调用该对象的 notify()
或 notifyAll()
方法,或者指定的时间(以毫秒为单位)过去。
- wait(long timeout, int nanos)
:与上一个类似,但增加了纳秒级别的精度。
public class WaitExample {
public static void main(String[] args) {
Object lock = new Object();
Thread thread1 = new Thread(() -> {
synchronized (lock) {
System.out.println("Thread 1: Starting...");
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Thread 1: Resumed...");
}
});
Thread thread2 = new Thread(() -> {
synchronized (lock) {
System.out.println("Thread 2: Starting...");
lock.notify();
System.out.println("Thread 2: Notified...");
}
});
thread1.start();
thread2.start();
}
}
在这个例子中,thread1
调用 lock.wait()
后进入等待状态并释放 lock
的锁。thread2
获得锁后调用 lock.notify()
唤醒 thread1
。
notify()
方法
notify()
方法用于唤醒在此对象监视器上等待的单个线程。
public class NotifyExample {
public static void main(String[] args) {
Object lock = new Object();
Thread thread1 = new Thread(() -> {
synchronized (lock) {
System.out.println("Thread 1: Starting...");
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Thread 1: Resumed...");
}
});
Thread thread2 = new Thread(() -> {
synchronized (lock) {
System.out.println("Thread 2: Starting...");
lock.notify();
System.out.println("Thread 2: Notified...");
}
});
thread1.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
thread2.start();
}
}
在这个例子中,thread2
调用 lock.notify()
唤醒了 thread1
。
notifyAll()
方法
notifyAll()
方法用于唤醒在此对象监视器上等待的所有线程。
public class NotifyAllExample {
public static void main(String[] args) {
Object lock = new Object();
Thread thread1 = new Thread(() -> {
synchronized (lock) {
System.out.println("Thread 1: Starting...");
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Thread 1: Resumed...");
}
});
Thread thread2 = new Thread(() -> {
synchronized (lock) {
System.out.println("Thread 2: Starting...");
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Thread 2: Resumed...");
}
});
Thread thread3 = new Thread(() -> {
synchronized (lock) {
System.out.println("Thread 3: Starting...");
lock.notifyAll();
System.out.println("Thread 3: Notified all...");
}
});
thread1.start();
thread2.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
thread3.start();
}
}
在这个例子中,thread3
调用 lock.notifyAll()
唤醒了 thread1
和 thread2
。
常见实践:生产者 - 消费者模型
生产者 - 消费者模型是多线程编程中的经典问题,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 {
queue.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
queue.add(value++);
System.out.println("Produced: " + (value - 1));
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 {
queue.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
int value = queue.poll();
System.out.println("Consumed: " + 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();
}
}
在这个例子中,Producer
线程在队列满时调用 queue.wait()
等待,Consumer
线程消费后调用 queue.notify()
唤醒 Producer
线程。反之,Consumer
线程在队列空时等待,Producer
线程生产后唤醒 Consumer
线程。
最佳实践
- 始终在
synchronized
块中调用wait()
、notify()
和notifyAll()
:这些方法必须在获取对象的锁之后调用,否则会抛出IllegalMonitorStateException
。 - 使用
while
循环检查条件:被唤醒的线程可能会出现“虚假唤醒”的情况,即没有被其他线程调用notify()
或notifyAll()
就被唤醒了。因此,应该在while
循环中检查等待的条件,确保条件满足后再继续执行。 - 合理选择
notify()
和notifyAll()
:如果只有一个线程在等待并且知道只有一个线程需要被唤醒,使用notify()
可以提高性能。如果有多个线程在等待并且都可能需要被唤醒,使用notifyAll()
。
小结
wait
和 notify
是 Java 多线程编程中实现线程间通信的重要机制。通过合理使用这两个方法,可以解决诸如生产者 - 消费者模型等多线程协作问题。在使用过程中,需要注意在 synchronized
块中调用,以及处理虚假唤醒等问题,遵循最佳实践可以确保代码的正确性和高效性。
参考资料
- Oracle Java Documentation - Object Class
- 《Effective Java》by Joshua Bloch
- 《Java Concurrency in Practice》by Brian Goetz
希望这篇博客能帮助你深入理解并高效使用 Java 中的 wait
和 notify
机制。如果你有任何问题或建议,欢迎在评论区留言。