Java Notify and Wait:线程间通信的关键机制
简介
在多线程编程中,线程间的有效通信至关重要。Java 的 notify()
和 wait()
方法提供了一种强大的机制,用于协调不同线程之间的操作。通过这两个方法,线程可以暂停执行(wait()
),并在特定条件满足时被其他线程唤醒(notify()
)。本文将深入探讨 notify()
和 wait()
的概念、使用方法、常见实践及最佳实践。
目录
- 基础概念
- 使用方法
wait()
方法notify()
方法notifyAll()
方法
- 常见实践
- 生产者 - 消费者模式
- 线程同步控制
- 最佳实践
- 避免死锁
- 合理使用
notifyAll()
- 条件判断使用
while
而非if
- 小结
- 参考资料
基础概念
wait()
和 notify()
是 java.lang.Object
类的方法,这意味着所有 Java 对象都具备这两个方法。它们主要用于线程间的通信,通过共享对象的监视器(monitor)来实现。
监视器(Monitor)
每个对象都有一个与之关联的监视器。当一个线程访问对象的同步方法或同步块时,它会获取该对象的监视器。在持有监视器期间,其他线程无法访问该对象的同步代码,直到该线程释放监视器。
wait()
方法
wait()
方法用于使当前线程等待,直到其他线程调用该对象的 notify()
或 notifyAll()
方法。调用 wait()
方法时,当前线程会释放对象的监视器,进入等待状态。当被唤醒后,线程会重新获取监视器,然后继续执行。
notify()
方法
notify()
方法用于唤醒在此对象监视器上等待的单个线程。如果有多个线程在等待,则选择其中一个唤醒,选择是任意的,由 JVM 决定。
notifyAll()
方法
notifyAll()
方法用于唤醒在此对象监视器上等待的所有线程。所有被唤醒的线程会竞争获取对象的监视器,然后继续执行。
使用方法
wait()
方法
wait()
方法有三种重载形式:
public final void wait() throws InterruptedException
public final void wait(long timeout) throws InterruptedException
public final void wait(long timeout, int nanos) throws InterruptedException
示例代码:
public class WaitExample {
public static void main(String[] args) {
Object lock = new Object();
Thread thread = new Thread(() -> {
synchronized (lock) {
try {
System.out.println("线程开始等待...");
lock.wait();
System.out.println("线程被唤醒...");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
thread.start();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lock) {
lock.notify();
System.out.println("主线程唤醒了等待线程");
}
}
}
notify()
方法
notify()
方法是 Object
类的实例方法,调用时需要在同步块或同步方法中进行,以确保线程持有对象的监视器。
示例代码:
public class NotifyExample {
public static void main(String[] args) {
Object lock = new Object();
Thread thread = new Thread(() -> {
synchronized (lock) {
try {
System.out.println("线程开始等待...");
lock.wait();
System.out.println("线程被唤醒...");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
thread.start();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lock) {
lock.notify();
System.out.println("主线程唤醒了等待线程");
}
}
}
notifyAll()
方法
notifyAll()
方法同样需要在同步块或同步方法中调用,用于唤醒所有等待在该对象监视器上的线程。
示例代码:
public class NotifyAllExample {
public static void main(String[] args) {
Object lock = new Object();
Thread thread1 = new Thread(() -> {
synchronized (lock) {
try {
System.out.println("线程1开始等待...");
lock.wait();
System.out.println("线程1被唤醒...");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
Thread thread2 = new Thread(() -> {
synchronized (lock) {
try {
System.out.println("线程2开始等待...");
lock.wait();
System.out.println("线程2被唤醒...");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
thread1.start();
thread2.start();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lock) {
lock.notifyAll();
System.out.println("主线程唤醒了所有等待线程");
}
}
}
常见实践
生产者 - 消费者模式
生产者 - 消费者模式是多线程编程中的经典场景,通过 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("生产: " + 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 {
queue.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
int value = queue.poll();
System.out.println("消费: " + 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();
}
}
线程同步控制
在某些场景下,需要控制多个线程按照特定顺序执行,wait()
和 notify()
可以实现这种同步。
public class ThreadSyncExample {
private static Object lock1 = new Object();
private static Object lock2 = new Object();
public static void main(String[] args) {
Thread thread1 = new Thread(() -> {
synchronized (lock1) {
System.out.println("线程1执行");
lock1.notify();
}
});
Thread thread2 = new Thread(() -> {
synchronized (lock1) {
try {
lock1.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程2执行");
synchronized (lock2) {
lock2.notify();
}
}
});
Thread thread3 = new Thread(() -> {
synchronized (lock2) {
try {
lock2.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程3执行");
}
});
thread2.start();
thread1.start();
thread3.start();
}
}
最佳实践
避免死锁
死锁是多线程编程中常见的问题,当两个或多个线程相互等待对方释放资源时,就会发生死锁。为避免死锁,应注意以下几点:
- 尽量减少锁的使用范围和时间。
- 确保线程获取锁的顺序一致。
- 使用 tryLock()
方法尝试获取锁,避免无限等待。
合理使用 notifyAll()
notifyAll()
会唤醒所有等待线程,可能导致性能问题。应尽量使用 notify()
唤醒单个线程,只有在需要唤醒所有等待线程时才使用 notifyAll()
。
条件判断使用 while
而非 if
在使用 wait()
方法时,应使用 while
循环进行条件判断,而不是 if
。因为线程被唤醒后,可能由于其他原因导致条件不再满足,使用 while
可以确保在条件真正满足时才继续执行。
synchronized (lock) {
while (!condition) {
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 执行相应操作
}
小结
notify()
和 wait()
方法是 Java 多线程编程中实现线程间通信的重要机制。通过合理使用这两个方法,可以实现复杂的多线程同步和协作。在实际应用中,需要注意避免死锁、合理使用 notifyAll()
以及正确使用条件判断,以确保程序的正确性和性能。
参考资料
- Oracle Java Documentation - Object Class
- 《Effective Java》 - Joshua Bloch
- 《Java Concurrency in Practice》 - Brian Goetz