Java中的wait()与notify()机制
简介
在多线程编程中,线程之间的协调与通信至关重要。Java提供了wait()
和notify()
方法,用于线程间的同步与通信。这两个方法是Object
类的一部分,意味着每个Java对象都自带了这种线程间通信的能力。理解并正确使用wait()
和notify()
能够有效地解决多线程环境下的复杂问题,如生产者-消费者模型。
目录
- 基础概念
wait()
方法notify()
方法notifyAll()
方法
- 使用方法
- 基本语法
- 调用条件
- 常见实践
- 生产者-消费者模型
- 线程间的简单同步
- 最佳实践
- 避免死锁
- 合理使用
wait()
的超时机制 - 确保正确的锁对象
- 小结
- 参考资料
基础概念
wait()
方法
wait()
方法用于使当前线程等待,直到其他线程调用该对象的notify()
或notifyAll()
方法。当一个线程调用wait()
方法时,它会释放对象的锁,进入等待状态。这是为了避免其他线程因为无法获取锁而一直阻塞。当wait()
的线程被唤醒后,它会重新尝试获取对象的锁,然后继续执行wait()
方法之后的代码。
notify()
方法
notify()
方法用于唤醒在此对象监视器上等待的单个线程。如果有多个线程在等待,那么会随机选择一个线程唤醒。被唤醒的线程会从等待状态变为可运行状态,但它不会立即执行,而是需要等待获取对象的锁。
notifyAll()
方法
notifyAll()
方法会唤醒在此对象监视器上等待的所有线程。所有被唤醒的线程都会竞争对象的锁,最终只有一个线程能够获取锁并继续执行,其他线程会再次进入阻塞状态等待锁的释放。
使用方法
基本语法
public class WaitNotifyExample {
public static void main(String[] args) {
Object lock = new Object();
Thread thread1 = new Thread(() -> {
synchronized (lock) {
try {
System.out.println("Thread 1: about to wait");
lock.wait();
System.out.println("Thread 1: woke up");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
Thread thread2 = new Thread(() -> {
synchronized (lock) {
System.out.println("Thread 2: about to notify");
lock.notify();
System.out.println("Thread 2: notified");
}
});
thread1.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
thread2.start();
}
}
调用条件
wait()
、notify()
和notifyAll()
方法必须在synchronized
块或方法中调用。这是因为这些方法的调用依赖于对象的监视器(锁),而synchronized
块或方法能够确保当前线程拥有对象的锁。- 调用
wait()
方法的线程必须是持有对象锁的线程。否则,会抛出IllegalMonitorStateException
异常。
常见实践
生产者-消费者模型
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 {
System.out.println("Queue is full, producer is waiting");
queue.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
queue.add(value++);
System.out.println("Produced: " + value);
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 {
System.out.println("Queue is empty, consumer is waiting");
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();
}
}
线程间的简单同步
public class SimpleSyncExample {
private static boolean flag = false;
public static void main(String[] args) {
Object lock = new Object();
Thread thread1 = new Thread(() -> {
synchronized (lock) {
while (!flag) {
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("Thread 1: flag is now true");
}
});
Thread thread2 = new Thread(() -> {
synchronized (lock) {
flag = true;
lock.notify();
System.out.println("Thread 2: set flag to true and notified");
}
});
thread1.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
thread2.start();
}
}
最佳实践
避免死锁
死锁是多线程编程中常见的问题,在使用wait()
和notify()
时尤其需要注意。为了避免死锁:
1. 确保所有线程获取锁的顺序一致。
2. 尽量减少锁的持有时间,避免长时间占用锁。
3. 使用wait()
的超时机制,防止线程无限期等待。
合理使用wait()
的超时机制
wait()
方法有一个带参数的重载版本,允许指定等待的最长时间。合理使用这个超时机制可以避免线程因为某些异常情况而一直等待。
synchronized (lock) {
try {
lock.wait(5000); // 等待5秒
} catch (InterruptedException e) {
e.printStackTrace();
}
}
确保正确的锁对象
在调用wait()
、notify()
和notifyAll()
时,要确保使用的是正确的锁对象。如果多个线程使用不同的锁对象,那么线程间的通信将无法正常进行。
小结
wait()
和notify()
是Java多线程编程中强大的工具,用于线程间的同步与通信。通过合理使用这两个方法,可以解决诸如生产者-消费者模型等复杂的多线程问题。在使用过程中,需要注意基本概念、调用条件以及遵循最佳实践,以避免常见的问题,如死锁。
参考资料
- 《Effective Java》 - Joshua Bloch
- 《Java Concurrency in Practice》 - Brian Goetz