JavaEE初阶——Java多线程通信:wait与notify详解
本文通过生产者-消费者模型演示了Java线程通信的wait/notify机制。代码展示了synchronized块中使用wait()和notify()实现线程协调,重点解析了Object类提供的这些方法特性:wait会释放锁并进入等待,notify/notifyAll用于唤醒线程。文中对比了wait与sleep的关键区别:wait用于线程间通信并释放锁,sleep仅暂停执行不释放锁。扩展说明中强调
目录
1. 完整的 Java 代码演示 (Producer-Consumer 模型)
A. 为什么 wait() 必须在 while 循环中调用?
B. IllegalMonitorStateException
这张图片展示的是 Java 多线程编程 中关于 线程通信(Thread Communication) 的核心知识点大纲,特别是围绕 Object 类提供的 wait() 和 notify() 机制。
根据您的要求,我将编写一段完整的 Java 代码来演示这些概念,逐行注释,然后详细解析图片中的知识点以及 wait 和 sleep 的经典面试题对比。
1. 完整的 Java 代码演示 (Producer-Consumer 模型)
为了演示 wait()、notify() 和 notifyAll(),最经典的场景是生产者-消费者模型。
Java
package com.example.thread;
// 定义一个简单的共享资源类
class SharedResource {
// 这是一个共享的数据变量
private String message;
// 这是一个标志位,true 表示有数据(Wait for consumer),false 表示没数据(Wait for producer)
private boolean hasMessage = false;
// 7.1 & 7.3: 生产数据的方法
public synchronized void produce(String newMessage) {
// 使用 while 循环而不是 if,防止“虚假唤醒”(Spurious Wakeup)
while (hasMessage) {
try {
// 打印当前状态,表示生产者需要等待
System.out.println("生产者: 队列已满,等待消费者消费...");
// 7.1 wait()方法:释放锁,当前线程进入等待状态
wait();
} catch (InterruptedException e) {
// 处理线程中断异常
Thread.currentThread().interrupt();
e.printStackTrace();
}
}
// 模拟生产数据的过程
this.message = newMessage;
this.hasMessage = true;
System.out.println("生产者: 已生产消息 -> " + newMessage);
// 7.2 / 7.3 notify() 或 notifyAll() 方法:唤醒等待的线程
// notify() 随机唤醒一个,notifyAll() 唤醒所有。这里演示 notify()
notify();
}
// 消费数据的方法
public synchronized void consume() {
// 如果没有消息,消费者需要等待
while (!hasMessage) {
try {
// 打印当前状态
System.out.println("消费者: 队列为空,等待生产者生产...");
// 7.1 wait()方法:释放锁,暂停执行
wait();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
e.printStackTrace();
}
}
// 模拟消费数据的过程
System.out.println("消费者: 已消费消息 -> " + this.message);
this.hasMessage = false;
// 唤醒生产者线程
notify();
}
}
// 测试主类
public class WaitNotifyDemo {
public static void main(String[] args) {
// 创建共享资源对象
SharedResource resource = new SharedResource();
// 创建生产者线程
Thread producerThread = new Thread(() -> {
// 生产 3 条数据
String[] messages = {"消息A", "消息B", "消息C"};
for (String msg : messages) {
resource.produce(msg);
try {
// 模拟生产耗时,sleep 不会释放锁,但这里是在同步块之外调用的
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "Producer-Thread");
// 创建消费者线程
Thread consumerThread = new Thread(() -> {
// 消费 3 条数据
for (int i = 0; i < 3; i++) {
resource.consume();
try {
// 模拟消费耗时
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "Consumer-Thread");
// 启动线程
producerThread.start();
consumerThread.start();
}
}
2. 图片涉及知识点详细解析
图片中的大纲涵盖了 Java 线程间通信的基础机制。所有这些方法都属于 java.lang.Object 类,而不是 Thread 类,这意味着任何对象都可以作为锁(Monitor)。
7.1 wait() 方法
-
定义: 使当前线程立即停止运行,释放对象锁,并进入等待队列(Wait Set)。
-
前提条件: 调用
wait()的线程必须拥有该对象的监视器锁(Monitor),即必须在synchronized代码块或方法中调用。 -
核心行为:
-
释放锁: 这是与
sleep()最关键的区别。 -
阻塞: 线程进入
WAITING或TIMED_WAITING状态。 -
等待唤醒: 直到其他线程调用同一个对象的
notify()或notifyAll()。
-
7.2 notify() 方法
-
定义: 唤醒在此对象监视器上等待的单个线程。
-
随机性:如果有多个线程在等待,选择唤醒哪一个是任意的(取决于具体的 JVM 实现),开发者无法指定唤醒哪一个。
-
锁的持有: 调用
notify()后,当前线程不会立即释放锁,而是要等到执行完当前的synchronized代码块后才释放。被唤醒的线程也要等到获得锁之后才能继续执行。
7.3 notifyAll() 方法
-
定义: 唤醒在此对象监视器上等待的所有线程。
-
行为: 所有等待的线程都会被唤醒,并开始竞争锁。只有一个线程能抢到锁并执行,其他的线程会再次进入阻塞状态(Blocked),等待锁被释放。
-
使用场景: 当有多个线程等待,且你不确定应该唤醒哪一个,或者多个线程等待的条件可能同时满足时,使用
notifyAll()更安全,可以避免“信号丢失”导致的死锁。
7.4 wait 和 sleep 的对比 (高频面试题)
这是 Java 面试中最常见的问题之一。
| 特性 | Object.wait() | Thread.sleep() |
| 所属类 | Object 类 |
Thread 类 |
| 锁的释放 | 释放锁 (Release Lock) | 不释放锁 (Hold Lock) |
| 使用范围 | 必须在 synchronized 块中 |
任何地方 |
| 用途 | 线程间通信 (Thread Communication) | 暂停执行 (Pause Execution) |
| 唤醒方式 | 需要 notify() / notifyAll() (除非设置超时) |
时间到了自动唤醒 |
| 异常 | 抛出 InterruptedException |
抛出 InterruptedException |
一句话总结:
wait是用来让线程之间协作的(交出控制权和锁),而sleep只是让线程休息一下(不交出锁)。
3. 详细扩展说明
为了深入理解,我们需要补充几个图里没写但非常重要的点:
A. 为什么 wait() 必须在 while 循环中调用?
在上面的代码中,我使用了:
Java
while (condition) {
wait();
}
而不是:
Java
if (condition) {
wait();
}
原因: 防止虚假唤醒 (Spurious Wakeup)。
-
在底层操作系统层面,线程有时会在没有收到 notify 信号的情况下莫名其妙地醒来。
-
如果使用
if,线程醒来后会直接往下执行,而不会再次检查条件是否满足,这可能导致程序逻辑错误(例如在队列为空时强行消费)。 -
使用
while,线程醒来后会再次检查条件,如果条件不满足,它会再次调用wait()继续等待。
B. IllegalMonitorStateException
如果你在没有 synchronized 的代码块中调用 wait() 或 notify(),程序会抛出 IllegalMonitorStateException 运行时异常。这是因为 Java 强制要求线程通信必须建立在持有锁的基础上。
C. 现代替代方案:Lock 和 Condition
虽然 wait/notify 是基础,但在 Java 5 引入 java.util.concurrent 包后,通常推荐使用更高级的 ReentrantLock 和 Condition。
-
synchronized$\rightarrow$ReentrantLock -
wait()$\rightarrow$Condition.await() -
notify()$\rightarrow$Condition.signal()
优势: Condition 允许通过创建多个 Condition 对象(例如 notFull 和 notEmpty),实现精准唤醒某类线程,而不是像 notifyAll 那样粗暴地唤醒所有线程。
下一步建议
您是否希望我把上面的 wait/notify 代码改写成使用 ReentrantLock 和 Condition 的版本,以便您对比新旧两种写法的区别?
更多推荐



所有评论(0)