Java 中 wait() 和 notify() 的正确使用方式(2025-2026 视角)
wait()、notify()、notifyAll() 是 Object 类 的原生方法,是 Java 最早提供的线程间协作机制(属于低级别、重量级的等待/通知机制)。
核心规则(必须全部记住,否则 100% 会出问题)
- 必须在 synchronized 块/方法中调用
- wait()、notify()、notifyAll() 都要求当前线程持有同一个对象的监视器锁(monitor)
- 否则抛出
IllegalMonitorStateException
- wait() 会释放锁
- 调用 wait() 后,当前线程会释放对象锁,进入该对象的等待队列(wait set)
- notify() / notifyAll() 不释放锁
- 只是唤醒等待队列中的一个/全部线程,但不立即把锁给被唤醒的线程
- 只有当前持有锁的线程离开 synchronized 块后,被唤醒的线程才有机会竞争锁
- 最经典的写法模板(生产级必须这样写)
// 消费者
synchronized (lock) {
while (conditionNotMet) { // 必须用 while,不是 if!(防止虚假唤醒)
lock.wait(); // 释放锁并等待
}
// 条件满足,消费
doConsume();
}
// 生产者
synchronized (lock) {
// 生产
doProduce();
lock.notify(); // 或 notifyAll()
// 离开 synchronized 块后,被唤醒的线程才有机会抢锁
}
为什么必须用 while 而不是 if?(虚假唤醒经典坑)
虚假唤醒(spurious wakeup):线程可能在没有被 notify 的情况下被系统唤醒(极少见,但 JVM 规范允许)。
// 错误写法(极易出问题)
if (queue.isEmpty()) {
lock.wait(); // 被虚假唤醒后,可能直接往下执行,而队列还是空的
}
// 正确写法(生产环境唯一推荐)
while (queue.isEmpty()) {
lock.wait();
}
完整经典示例:生产者-消费者(固定大小队列)
import java.util.LinkedList;
import java.util.Queue;
public class ProducerConsumer {
private final Queue<Integer> queue = new LinkedList<>();
private final int MAX_SIZE = 10;
private final Object lock = new Object();
class Producer implements Runnable {
@Override
public void run() {
try {
while (true) {
synchronized (lock) {
while (queue.size() == MAX_SIZE) {
System.out.println("队列已满,生产者等待...");
lock.wait();
}
int item = (int) (Math.random() * 100);
queue.offer(item);
System.out.println("生产: " + item + ",当前大小: " + queue.size());
lock.notifyAll(); // 唤醒所有等待的消费者
}
Thread.sleep(500); // 模拟生产耗时
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
class Consumer implements Runnable {
@Override
public void run() {
try {
while (true) {
synchronized (lock) {
while (queue.isEmpty()) {
System.out.println("队列为空,消费者等待...");
lock.wait();
}
int item = queue.poll();
System.out.println("消费: " + item + ",当前大小: " + queue.size());
lock.notifyAll(); // 唤醒可能等待的生产者
}
Thread.sleep(800); // 模拟消费耗时
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
public static void main(String[] args) {
ProducerConsumer pc = new ProducerConsumer();
new Thread(pc.new Producer(), "生产者-1").start();
new Thread(pc.new Consumer(), "消费者-1").start();
new Thread(pc.new Consumer(), "消费者-2").start();
}
}
常见错误写法汇总(你几乎一定会踩)
| 错误写法 | 后果 | 正确做法 |
|---|---|---|
| 在 synchronized 外面调用 wait() | IllegalMonitorStateException | 必须在 synchronized 内 |
| 用 if 判断条件而不是 while | 虚假唤醒导致逻辑错误 | 永远用 while |
| 只用 notify() 而不用 notifyAll() | 可能导致部分线程永久等待(信号丢失) | 多消费者/生产者场景用 notifyAll |
| notify() 后立即修改共享变量 | 可能导致被唤醒线程看到旧状态 | 修改完再 notify |
| 不同对象上 wait/notify | 线程永远唤不醒 | 必须用同一个锁对象 |
2025–2026 年真实项目中的选择建议
| 场景 | 推荐工具 | 原因 |
|---|---|---|
| 简单生产者-消费者、线程间状态等待 | wait/notify | 轻量、无额外依赖 |
| 需要超时等待 | Condition.await(long, TimeUnit) | ReentrantLock 的 Condition 更灵活 |
| 大多数现代业务代码 | BlockingQueue(ArrayBlockingQueue) | 封装好了 wait/notify,API 更安全 |
| 高并发、复杂条件等待 | Condition + ReentrantLock | 支持多个等待队列、公平锁、可中断 |
| 响应式/异步场景 | CompletableFuture / reactor | 基本不再用原始 wait/notify |
一句话总结(面试/生产最常问的答案):
“wait() 和 notify() 必须在同一个对象的 synchronized 块中使用,wait() 会释放锁并进入等待队列,notify() 只唤醒但不释放锁,永远用 while 判断条件,多线程协作场景优先考虑 notifyAll()。”
如果你有具体的场景(比如多个等待条件、带超时的等待、与线程池结合等),可以告诉我,我可以给你更针对性的写法。