Java Object notify() 方法详解:线程协作的“唤醒信号”
在 Java 多线程编程中,线程之间的协作是构建高效、稳定并发程序的核心。当我们需要让一个线程等待某个条件满足后再继续执行时,wait() 和 notify() 就成了必不可少的工具。今天我们就来深入聊聊 Java Object notify() 方法,它是实现线程间通信的重要机制之一。
想象一下,你在餐厅等位吃饭。服务员告诉你“菜好了就叫你”,你坐在位置上等待,直到服务员喊你。这个“叫你”的动作,就类似于 notify() 的作用——它通知一个或多个正在等待的线程,某个条件已经满足,可以继续执行了。
而 notify() 必须配合 wait() 使用,它们都属于 Object 类的方法,这意味着每个对象都可以作为锁对象,用于控制线程的等待与唤醒。
为什么需要 notify()?线程等待的场景解析
在并发编程中,我们经常遇到这样的场景:某个线程需要等待某个资源就绪,比如:
- 缓冲区为空,消费者线程必须等待生产者放入数据;
- 线程池中没有空闲线程,新任务必须等待;
- 数据库连接池中的连接被占用,请求线程需要等待释放。
这些场景下,如果线程一直“忙等”(即不断循环检查条件),会严重浪费 CPU 资源。因此,Java 提供了 wait() 方法让线程主动释放锁并进入等待状态,直到被其他线程唤醒。
而 notify() 就是唤醒等待线程的关键。它不直接唤醒线程,而是通知一个正在等待该对象锁的线程,使其从 wait() 状态中恢复,重新尝试获取锁并继续执行。
⚠️ 注意:
notify()只能唤醒一个线程,如果多个线程在等待,Java 会随机选择一个。若要唤醒所有等待线程,应使用notifyAll()。
notify() 的使用前提:同步块与锁机制
notify() 不能随意调用,它必须在当前线程持有该对象的监视器锁(即 synchronized 锁)的情况下才能执行。否则会抛出 IllegalMonitorStateException。
我们来看一个典型的使用场景:生产者-消费者模型。
public class ProducerConsumerExample {
private final Object lock = new Object();
private boolean isProduced = false;
// 生产者线程
public void produce() {
synchronized (lock) {
// 如果已经有产品,就等待
while (isProduced) {
try {
System.out.println("生产者:已有产品,等待消费...");
lock.wait(); // 释放锁并进入等待状态
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
System.out.println("生产者被中断");
return;
}
}
// 模拟生产过程
System.out.println("生产者:正在生产...");
isProduced = true;
System.out.println("生产者:产品已生产完成");
// 唤醒等待的消费者
lock.notify(); // 通知一个正在等待的线程
System.out.println("生产者:已通知消费者");
}
}
// 消费者线程
public void consume() {
synchronized (lock) {
// 如果没有产品,就等待
while (!isProduced) {
try {
System.out.println("消费者:无产品,等待生产...");
lock.wait(); // 释放锁并等待
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
System.out.println("消费者被中断");
return;
}
}
// 消费产品
System.out.println("消费者:正在消费...");
isProduced = false;
System.out.println("消费者:产品已消费");
// 唤醒等待的生产者
lock.notify(); // 通知生产者
System.out.println("消费者:已通知生产者");
}
}
public static void main(String[] args) {
ProducerConsumerExample example = new ProducerConsumerExample();
Thread producer = new Thread(() -> example.produce());
Thread consumer = new Thread(() -> example.consume());
// 启动线程
producer.start();
consumer.start();
try {
producer.join();
consumer.join();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
代码说明:
synchronized (lock)确保了只有一个线程能进入临界区;wait()会释放lock锁,并让当前线程进入等待队列;notify()会唤醒一个在该锁上等待的线程(不是立即执行,而是进入“就绪队列”);while循环用于避免虚假唤醒(spurious wakeup),这是 Java 官方建议的写法。
notify() 与 notifyAll() 的区别:唤醒一个还是全部?
虽然 notify() 很常用,但它只唤醒一个等待线程。这在某些场景下可能导致“死锁”或“资源浪费”。
举个例子:假设你有 3 个消费者线程在等待,生产者只调用一次 notify(),那么只有一个消费者被唤醒,其他两个仍然在等待。如果下一个生产事件还没发生,那这 2 个消费者可能永远等不到通知。
因此,当多个线程对同一条件感兴趣时,推荐使用 notifyAll()。它会唤醒所有在该对象上等待的线程,让它们重新竞争锁。
| 方法 | 唤醒数量 | 适用场景 |
|---|---|---|
notify() |
1 个 | 只有一个线程需要被唤醒 |
notifyAll() |
所有等待线程 | 多个线程共享资源,需全部参与判断 |
✅ 建议:除非你明确知道只需要唤醒一个线程,否则优先使用
notifyAll(),更安全。
notify() 的执行时机与线程调度
notify() 的调用时机非常关键。它只是“发信号”,并不保证被唤醒的线程会立刻执行。因为:
- 被唤醒的线程需要重新获取锁;
- JVM 的线程调度器决定何时执行;
- 可能存在其他高优先级线程正在运行。
我们来修改上面的例子,加入线程优先级控制,观察 notify() 的行为:
public class NotifyTimingExample {
private final Object lock = new Object();
private boolean flag = false;
public void signal() {
synchronized (lock) {
System.out.println("信号线程:准备唤醒等待线程");
lock.notify(); // 发出唤醒信号
System.out.println("信号线程:已发出通知");
// 模拟信号发出后的延迟
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
public void waitAndCheck() {
synchronized (lock) {
System.out.println("等待线程:开始等待...");
try {
lock.wait(); // 释放锁,进入等待
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return;
}
System.out.println("等待线程:被唤醒,检查条件");
System.out.println("等待线程:flag = " + flag);
}
}
public static void main(String[] args) {
NotifyTimingExample example = new NotifyTimingExample();
Thread signalThread = new Thread(() -> example.signal());
Thread waitThread = new Thread(() -> example.waitAndCheck());
waitThread.setPriority(Thread.MIN_PRIORITY); // 设置优先级低
signalThread.setPriority(Thread.MAX_PRIORITY); // 设置优先级高
waitThread.start();
try {
Thread.sleep(500); // 让等待线程先进入 wait
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
signalThread.start();
try {
signalThread.join();
waitThread.join();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
运行结果中你会看到:虽然 notify() 先执行,但等待线程仍需等待锁空闲后才能继续。这说明 notify() 只是“通知”,并非“立即执行”。
常见错误与最佳实践
在使用 Java Object notify() 方法 时,开发者常犯以下错误:
错误 1:在非 synchronized 块中调用 notify()
// ❌ 错误示例
lock.notify(); // 抛出 IllegalMonitorStateException
✅ 正确做法:必须在 synchronized 块中调用。
错误 2:用 if 替代 while 判断条件
// ❌ 错误
if (!isProduced) {
lock.wait();
}
// 若发生虚假唤醒,线程会继续执行,导致逻辑错误
// ✅ 正确
while (!isProduced) {
lock.wait();
}
错误 3:忘记调用 notify(),导致线程永远等待
这是最常见的死锁原因之一。建议在每个 wait() 的路径中,都确保有对应的 notify() 被执行。
总结:掌握 notify() 的核心要点
notify()是Object类的方法,用于唤醒一个正在等待该对象锁的线程;- 必须在
synchronized块中调用,否则抛出异常; - 与
wait()配合使用,实现线程间的协作; - 使用
while循环判断条件,防止虚假唤醒; - 优先考虑
notifyAll(),避免线程“饥饿”; - 唤醒后线程仍需竞争锁,不能立即执行。
掌握 Java Object notify() 方法,是你迈向高级并发编程的第一步。它虽然简单,但背后蕴含着线程调度、锁机制、资源竞争等复杂概念。理解它,不仅能写出更健壮的代码,还能在面试中游刃有余。
记住:线程之间的通信,不是靠“喊”,而是靠“信号”——notify() 就是那个关键的信号灯。