Java 中的等待线程(Wait Thread)
简介
在 Java 多线程编程中,wait
线程机制是一项关键特性,它允许线程之间进行有效的通信和同步。通过 wait
方法,一个线程可以暂停执行,释放对象的锁,并等待其他线程的通知。这在多个线程需要协调工作、共享资源或按照特定顺序执行的场景中非常有用。本文将深入探讨 Java 中 wait
线程的基础概念、使用方法、常见实践以及最佳实践。
目录
- 基础概念
- 使用方法
- 调用
wait
方法 - 调用
notify
和notifyAll
方法
- 调用
- 常见实践
- 生产者 - 消费者模式
- 线程同步控制
- 最佳实践
- 避免死锁
- 合理使用
wait
和notify
- 小结
- 参考资料
基础概念
在 Java 中,每个对象都有一个内部锁(也称为监视器锁)。当一个线程访问对象的同步方法或同步代码块时,它会自动获取该对象的锁。wait
方法是 Object
类的成员方法,它只能在同步代码块或同步方法中调用。当一个线程调用对象的 wait
方法时,它会释放该对象的锁,并进入等待状态,直到其他线程调用该对象的 notify
或 notifyAll
方法。
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();
System.out.println("等待线程被唤醒");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
waitingThread.start();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Thread notifyingThread = new Thread(() -> {
synchronized (lock) {
System.out.println("通知线程开始执行");
lock.notify();
System.out.println("通知线程发出通知");
}
});
notifyingThread.start();
}
}
在这个示例中,waitingThread
进入同步代码块后调用 lock.wait()
,释放 lock
的锁并进入等待状态。notifyingThread
在延迟 2 秒后启动,进入同步代码块并调用 lock.notify()
,唤醒 waitingThread
。
调用 notify
和 notifyAll
方法
notify
方法只唤醒一个等待线程,而 notifyAll
方法唤醒所有等待线程。下面是一个使用 notifyAll
的示例:
public class NotifyAllExample {
public static void main(String[] args) {
Object lock = new Object();
Thread waitingThread1 = new Thread(() -> {
synchronized (lock) {
System.out.println("等待线程 1 开始执行");
try {
lock.wait();
System.out.println("等待线程 1 被唤醒");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
Thread waitingThread2 = new Thread(() -> {
synchronized (lock) {
System.out.println("等待线程 2 开始执行");
try {
lock.wait();
System.out.println("等待线程 2 被唤醒");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
waitingThread1.start();
waitingThread2.start();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Thread notifyingThread = new Thread(() -> {
synchronized (lock) {
System.out.println("通知线程开始执行");
lock.notifyAll();
System.out.println("通知线程发出通知");
}
});
notifyingThread.start();
}
}
在这个示例中,waitingThread1
和 waitingThread2
都调用 lock.wait()
进入等待状态。notifyingThread
调用 lock.notifyAll()
唤醒这两个等待线程。
常见实践
生产者 - 消费者模式
生产者 - 消费者模式是多线程编程中的经典模式,wait
和 notify
方法常用于实现该模式。下面是一个简单的生产者 - 消费者示例:
import java.util.LinkedList;
import java.util.Queue;
class ProducerConsumerExample {
private static final int MAX_SIZE = 5;
private Queue<Integer> queue = new LinkedList<>();
public void produce() throws InterruptedException {
int value = 0;
while (true) {
synchronized (this) {
while (queue.size() == MAX_SIZE) {
System.out.println("队列已满,生产者等待");
wait();
}
queue.add(value++);
System.out.println("生产者生产: " + value);
notify();
}
Thread.sleep(1000);
}
}
public void consume() throws InterruptedException {
while (true) {
synchronized (this) {
while (queue.isEmpty()) {
System.out.println("队列已空,消费者等待");
wait();
}
int item = queue.poll();
System.out.println("消费者消费: " + item);
notify();
}
Thread.sleep(1000);
}
}
}
public class Main {
public static void main(String[] args) {
ProducerConsumerExample pc = new ProducerConsumerExample();
Thread producerThread = new Thread(() -> {
try {
pc.produce();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
Thread consumerThread = new Thread(() -> {
try {
pc.consume();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
producerThread.start();
consumerThread.start();
}
}
在这个示例中,生产者线程在队列满时调用 wait
等待,消费者线程在队列空时调用 wait
等待。当生产者生产数据或消费者消费数据时,调用 notify
唤醒对方线程。
线程同步控制
wait
和 notify
方法还可以用于控制线程的执行顺序。例如,让一个线程等待另一个线程完成特定操作后再继续执行:
public class ThreadSyncExample {
private static boolean flag = false;
public static void main(String[] args) {
Object lock = new Object();
Thread thread1 = new Thread(() -> {
synchronized (lock) {
System.out.println("线程 1 开始执行");
// 执行一些操作
flag = true;
lock.notify();
}
});
Thread thread2 = new Thread(() -> {
synchronized (lock) {
System.out.println("线程 2 开始执行");
while (!flag) {
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("线程 2 继续执行");
}
});
thread2.start();
thread1.start();
}
}
在这个示例中,thread2
等待 thread1
将 flag
设置为 true
并调用 notify
后才继续执行。
最佳实践
避免死锁
死锁是多线程编程中常见的问题,当两个或多个线程相互等待对方释放资源时就会发生死锁。为了避免死锁,应遵循以下原则:
- 获取锁的顺序一致:在多个线程中,按照相同的顺序获取锁。
- 避免嵌套锁:尽量减少锁的嵌套使用,简化锁的层次结构。
- 设置合理的超时时间:使用 tryLock
方法并设置超时时间,避免线程无限期等待。
合理使用 wait
和 notify
- 使用
while
循环检查条件:在调用wait
方法前,使用while
循环检查等待条件,以防止虚假唤醒(即线程在没有被notify
或notifyAll
唤醒的情况下醒来)。 - 确保锁的一致性:在调用
wait
、notify
和notifyAll
方法时,确保所有相关线程都使用同一个对象的锁。 - 避免过度通知:尽量使用
notify
方法唤醒单个线程,只有在需要唤醒所有等待线程时才使用notifyAll
方法,以提高性能。
小结
本文介绍了 Java 中 wait
线程的基础概念、使用方法、常见实践以及最佳实践。wait
和 notify
方法是 Java 多线程编程中实现线程通信和同步的重要工具,正确使用它们可以解决许多复杂的多线程问题。在实际应用中,需要注意避免死锁、合理使用 wait
和 notify
,以确保程序的正确性和性能。