Java多线程通信:wait/notify与sleep的深度剖析(时序图详解)
在Java多线程编程中,线程间的通信与协作是实现复杂并发逻辑的关键。wait()
、notify()
以及sleep()
方法作为线程控制的重要工具,有着各自独特的使用场景与规则。本文将深入探讨wait()
和notify()
的协作机制,以及sleep()
的阻塞特性,同时重点解析wait()
必须在循环中调用的核心原因——防止虚假唤醒(Spurious Wakeup)。
一、wait/notify:线程间通信的桥梁
1.1 wait/notify的核心功能
wait()
和notify()
是Object类的方法,主要用于线程间的协作与通信。
wait()
:当一个线程调用wait()
方法时,它会释放当前持有的对象锁,并进入等待状态。直到其他线程调用同一个对象的notify()
或notifyAll()
方法将其唤醒。notify()
:随机唤醒一个在该对象上等待的线程;而notifyAll()
则会唤醒所有在该对象上等待的线程。
关键操作时序图
1.2 wait/notify的使用规则
wait()
和notify()
必须在synchronized
同步代码块或同步方法中使用,这是因为它们的操作依赖于对象锁的状态。若不在同步环境中调用,会抛出IllegalMonitorStateException
异常。
1.3 典型使用场景:生产者-消费者模型
class SharedResource {private int data;private boolean isEmpty = true;public synchronized void produce(int value) {while (!isEmpty) {try {wait();} catch (InterruptedException e) {e.printStackTrace();}}data = value;isEmpty = false;notifyAll();}public synchronized void consume() {while (isEmpty) {try {wait();} catch (InterruptedException e) {e.printStackTrace();}}System.out.println("Consumed: " + data);isEmpty = true;notifyAll();}
}
在上述代码中,生产者线程在缓冲区满时调用wait()
等待,消费者线程消费数据后通过notifyAll()
唤醒生产者;反之亦然。
二、sleep:简单的线程阻塞
2.1 sleep的特性
sleep()
是Thread类的静态方法,用于让当前线程暂停执行指定的时间(以毫秒为单位)。与wait()
不同,sleep()
不会释放线程持有的锁。
2.2 使用场景
- 模拟延迟:在测试代码或需要控制执行节奏的场景中,通过
sleep()
让线程暂停一段时间。 - 避免过度占用资源:例如在轮询检查某个条件时,加入
sleep()
可以减少CPU的占用。
public class SleepExample {public static void main(String[] args) {Thread thread = new Thread(() -> {synchronized (SleepExample.class) {try {System.out.println("线程开始睡眠");Thread.sleep(2000);System.out.println("线程睡眠结束");} catch (InterruptedException e) {e.printStackTrace();}}});thread.start();}
}
在上述代码中,线程在持有锁的情况下进行睡眠,期间其他线程无法获取该锁。
三、为什么wait()必须在循环中调用:防止虚假唤醒
3.1 什么是虚假唤醒
虚假唤醒(Spurious Wakeup)是指线程在没有被其他线程调用notify()
或notifyAll()
的情况下被唤醒。虽然这种情况在实际中并不常见,但Java的wait()
机制允许它发生,这是JVM底层实现的结果。
3.2 循环检查的必要性
如果不使用循环,当发生虚假唤醒时,线程会误以为等待的条件已经满足,继续执行后续代码,可能导致程序逻辑错误。因此,wait()
必须配合while
循环使用,每次被唤醒后重新检查条件是否真正满足。
synchronized (obj) {while (!condition) {obj.wait();}// 条件满足,执行相应逻辑
}
通过while
循环,即使发生虚假唤醒,线程也会重新进入等待状态,直到条件真正满足才继续执行。
四、总结
wait/notify
:用于线程间的通信与协作,必须在同步环境中使用,且wait()
要配合while
循环防止虚假唤醒。sleep
:单纯的线程阻塞,不释放锁,适用于控制线程执行节奏。
理解这些方法的特性与使用规则,是编写正确、高效的多线程程序的基础。在实际开发中,合理运用这些工具,可以实现复杂的线程协作逻辑,同时避免因不当使用导致的各种问题。