wait和notify机制详解
方法作用注意事项wait()释放锁并等待必须在synchronized块中notify()唤醒一个等待的线程不立即释放锁唤醒所有等待的线程更安全,推荐使用。
·
wait和notify机制详解
为什么需要wait/notify
问题场景:主动等待 vs 被动通知
####场景:厨师与服务员
// ❌ 不好的实现:忙等待(Busy Waiting)
public class BadWaiting {
private static boolean foodReady = false;
public static void main(String[] args) {
// 服务员线程
Thread waiter = new Thread(() -> {
System.out.println("服务员:等待食物...");
// 不停地检查,浪费CPU
while (!foodReady) {
// 忙等待,CPU空转
}
System.out.println("服务员:食物好了,去送餐");
}, "服务员");
// 厨师线程
Thread chef = new Thread(() -> {
System.out.println("厨师:开始做饭");
try {
Thread.sleep(3000); // 模拟做饭
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("厨师:食物做好了");
foodReady = true;
}, "厨师");
waiter.start();
chef.start();
}
}
问题:
- ❌ 服务员线程一直循环检查,浪费CPU
- ❌ 即使厨师还没开始做,服务员也在空转
- ❌ 效率低下
// ✅ 好的实现:wait/notify
public class GoodWaiting {
private static boolean foodReady = false;
private static final Object lock = new Object();
public static void main(String[] args) {
// 服务员线程
Thread waiter = new Thread(() -> {
synchronized (lock) {
System.out.println("服务员:等待食物...");
try {
while (!foodReady) {
lock.wait(); // 释放锁,进入等待,不消耗CPU
}
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("服务员:食物好了,去送餐");
}
}, "服务员");
// 厨师线程
Thread chef = new Thread(() -> {
System.out.println("厨师:开始做饭");
try {
Thread.sleep(3000); // 模拟做饭
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lock) {
System.out.println("厨师:食物做好了");
foodReady = true;
lock.notify(); // 通知服务员
}
}, "厨师");
waiter.start();
chef.start();
}
}
优点:
- ✅ 服务员等待时不消耗CPU
- ✅ 厨师做好后主动通知,响应及时
- ✅ 效率高
wait/notify基本用法
核心方法
Object类提供的三个方法:
public class Object {
// 等待,直到被notify/notifyAll或中断
public final void wait() throws InterruptedException
// 等待指定时间(毫秒)
public final void wait(long timeout) throws InterruptedException
// 等待指定时间(毫秒+纳秒)
public final void wait(long timeout, int nanos) throws InterruptedException
// 唤醒一个等待的线程
public final native void notify()
// 唤醒所有等待的线程
public final native void notifyAll()
}
基本使用规则
规则1:必须在synchronized块中调用
// ❌ 错误:不在synchronized块中
public class WrongUsage {
private Object lock = new Object();
public void method() {
try {
lock.wait(); // IllegalMonitorStateException!
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
// ✅ 正确:在synchronized块中
public class CorrectUsage {
private Object lock = new Object();
public void method() {
synchronized (lock) {
try {
lock.wait(); // 正确
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
原因:wait/notify操作的是Monitor,必须先获得Monitor的ownership。
规则2:使用while而不是if检查条件
// ❌ 错误:使用if
synchronized (lock) {
if (!condition) {
lock.wait(); // 虚假唤醒时会直接继续执行
}
// 执行操作
}
// ✅ 正确:使用while
synchronized (lock) {
while (!condition) {
lock.wait(); // 虚假唤醒后会重新检查条件
}
// 执行操作
}
原因:防止虚假唤醒(Spurious Wakeup)。
wait()方法详解
wait()的作用
当前线程调用lock.wait()时:
步骤1:释放锁
Owner = null
步骤2:当前线程进入WaitSet
状态变为WAITING
步骤3:不再消耗CPU
线程阻塞等待
步骤4:被唤醒后
从WaitSet移到EntryList
重新竞争锁
步骤5:获得锁后
从wait()返回
继续执行
wait(long timeout)
synchronized (lock) {
try {
lock.wait(5000); // 最多等待5秒
} catch (InterruptedException e) {
e.printStackTrace();
}
// 被唤醒或超时后继续执行
}
两种退出方式:
- 被notify/notifyAll唤醒
- 超时自动唤醒
notify()方法详解
notify() vs notifyAll()
// notify():只唤醒一个等待的线程
synchronized (lock) {
lock.notify(); // 唤醒WaitSet中的一个线程(随机)
}
// notifyAll():唤醒所有等待的线程
synchronized (lock) {
lock.notifyAll(); // 唤醒WaitSet中的所有线程
}
选择建议:
- 如果只需要唤醒一个线程:
notify() - 如果不确定唤醒哪个:
notifyAll()(更安全)
notify()不会立即释放锁
重要:调用notify()后,当前线程继续持有锁!
public class NotifyNoRelease {
private static final Object lock = new Object();
public static void main(String[] args) throws InterruptedException {
// 等待线程
Thread t1 = new Thread(() -> {
synchronized (lock) {
System.out.println("t1: 开始等待");
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("t1: 被唤醒,继续执行");
}
}, "t1");
// 通知线程
Thread t2 = new Thread(() -> {
synchronized (lock) {
System.out.println("t2: 准备唤醒t1");
lock.notify();
System.out.println("t2: 已调用notify,但还持有锁");
try {
Thread.sleep(2000); // 继续持有锁2秒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("t2: 即将释放锁");
}
System.out.println("t2: 已释放锁");
}, "t2");
t1.start();
Thread.sleep(100); // 确保t1先wait
t2.start();
}
}
输出:
t1: 开始等待
t2: 准备唤醒t1
t2: 已调用notify,但还持有锁
(等待2秒)
t2: 即将释放锁
t2: 已释放锁
t1: 被唤醒,继续执行 ← t2释放锁后,t1才能继续
wait/notify原理
wait()方法完整流程图
notify()方法完整流程图
notifyAll()方法流程图
Monitor状态转换完整时序图
┌───────────────────────────────────────────────────────────────────┐
│ wait/notify 的 Monitor 状态完整转换过程 │
└───────────────────────────────────────────────────────────────────┘
时刻T1: Thread-1获取锁
┌──────────────┐
│ Owner │
│ Thread-1 ✅ │
├──────────────┤
│ EntryList │
│ (空) │
├──────────────┤
│ WaitSet │
│ (空) │
└──────────────┘
时刻T2: Thread-1调用wait()
┌──────────────┐
│ Owner │
│ null ⚠️ │ ← 锁被释放
├──────────────┤
│ EntryList │
│ (空) │
├──────────────┤
│ WaitSet │
│ Thread-1 😴 │ ← 进入等待集合,WAITING状态
└──────────────┘
时刻T3: Thread-2竞争锁成功
┌──────────────┐
│ Owner │
│ Thread-2 ✅ │
├──────────────┤
│ EntryList │
│ (空) │
├──────────────┤
│ WaitSet │
│ Thread-1 😴 │
└──────────────┘
时刻T4: Thread-2调用notify()
┌──────────────┐
│ Owner │
│ Thread-2 ✅ │ ← Thread-2继续持有锁!
├──────────────┤
│ EntryList │
│ Thread-1 🚶 │ ← Thread-1被移到这里,BLOCKED状态
├──────────────┤
│ WaitSet │
│ (空) │
└──────────────┘
时刻T5: Thread-2释放锁
┌──────────────┐
│ Owner │
│ null ⚠️ │ ← 锁被释放
├──────────────┤
│ EntryList │
│ Thread-1 💪 │ ← Thread-1尝试竞争锁
├──────────────┤
│ WaitSet │
│ (空) │
└──────────────┘
时刻T6: Thread-1重新获得锁
┌──────────────┐
│ Owner │
│ Thread-1 ✅ │ ← Thread-1重新获得锁
├──────────────┤
│ EntryList │
│ (空) │
├──────────────┤
│ WaitSet │
│ (空) │
└──────────────┘
Thread-1从wait()返回,继续执行 ✅
Monitor视角
初始状态:
┌──────────────┐
│ Owner │
│ null │
├──────────────┤
│ EntryList │
│ (空) │
├──────────────┤
│ WaitSet │
│ (空) │
└──────────────┘
Thread-1获取锁:
┌──────────────┐
│ Owner │
│ Thread-1 ←── │ 执行中
├──────────────┤
│ EntryList │
│ (空) │
├──────────────┤
│ WaitSet │
│ (空) │
└──────────────┘
Thread-1调用wait():
┌──────────────┐
│ Owner │
│ null ←── │ 锁被释放
├──────────────┤
│ EntryList │
│ (空) │
├──────────────┤
│ WaitSet │
│ Thread-1 ←── │ 进入等待集合
└──────────────┘
Thread-2获取锁并notify():
┌──────────────┐
│ Owner │
│ Thread-2 │ 继续执行
├──────────────┤
│ EntryList │
│ Thread-1 ←── │ 被移到这里
├──────────────┤
│ WaitSet │
│ (空) │
└──────────────┘
Thread-2释放锁,Thread-1获得锁:
┌──────────────┐
│ Owner │
│ Thread-1 ←── │ 重新获得锁
├──────────────┤
│ EntryList │
│ (空) │
├──────────────┤
│ WaitSet │
│ (空) │
└──────────────┘
虚假唤醒(Spurious Wakeup)
什么是虚假唤醒:线程在没有被notify/notifyAll的情况下,从wait()返回。
原因:
- 操作系统层面的实现细节
- 线程可能被信号中断
- JVM实现的优化
解决方法:使用while循环检查条件
// ❌ 使用if,虚假唤醒会导致问题
synchronized (lock) {
if (!condition) {
lock.wait();
}
// 虚假唤醒后,condition可能还是false,但会继续执行
performAction(); // 错误!
}
// ✅ 使用while,虚假唤醒后重新检查
synchronized (lock) {
while (!condition) {
lock.wait();
}
// 只有condition为true才会执行
performAction(); // 正确!
}
经典应用模式
模式1:生产者-消费者
单生产者-单消费者
import java.util.LinkedList;
import java.util.Queue;
public class ProducerConsumer {
private static final int MAX_SIZE = 5;
private Queue<Integer> queue = new LinkedList<>();
private final Object lock = new Object();
// 生产者
public void produce() {
int value = 0;
while (true) {
synchronized (lock) {
// 队列满了,等待
while (queue.size() == MAX_SIZE) {
try {
System.out.println("队列已满,生产者等待");
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 生产
queue.add(value);
System.out.println("生产: " + value + ",队列大小: " + queue.size());
value++;
// 通知消费者
lock.notify();
}
// 模拟生产耗时
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
// 消费者
public void consume() {
while (true) {
synchronized (lock) {
// 队列空了,等待
while (queue.isEmpty()) {
try {
System.out.println("队列为空,消费者等待");
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 消费
int value = queue.poll();
System.out.println("消费: " + value + ",队列大小: " + queue.size());
// 通知生产者
lock.notify();
}
// 模拟消费耗时
try {
Thread.sleep(1500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
ProducerConsumer pc = new ProducerConsumer();
Thread producer = new Thread(() -> pc.produce(), "生产者");
Thread consumer = new Thread(() -> pc.consume(), "消费者");
producer.start();
consumer.start();
}
}
运行结果:
生产: 0,队列大小: 1
生产: 1,队列大小: 2
消费: 0,队列大小: 1
生产: 2,队列大小: 2
消费: 1,队列大小: 1
生产: 3,队列大小: 2
...
多生产者-多消费者
public class MultiProducerConsumer {
private static final int MAX_SIZE = 5;
private Queue<Integer> queue = new LinkedList<>();
private final Object lock = new Object();
private int value = 0;
// 生产者
public void produce() {
while (true) {
synchronized (lock) {
while (queue.size() == MAX_SIZE) {
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
int item = value++;
queue.add(item);
System.out.println(Thread.currentThread().getName() +
" 生产: " + item + ",队列: " + queue.size());
lock.notifyAll(); // 多个消费者,使用notifyAll
}
try {
Thread.sleep((int)(Math.random() * 1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
// 消费者
public void consume() {
while (true) {
synchronized (lock) {
while (queue.isEmpty()) {
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
int item = queue.poll();
System.out.println(Thread.currentThread().getName() +
" 消费: " + item + ",队列: " + queue.size());
lock.notifyAll(); // 多个生产者,使用notifyAll
}
try {
Thread.sleep((int)(Math.random() * 1500));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
MultiProducerConsumer mpc = new MultiProducerConsumer();
// 2个生产者
for (int i = 1; i <= 2; i++) {
new Thread(() -> mpc.produce(), "生产者-" + i).start();
}
// 3个消费者
for (int i = 1; i <= 3; i++) {
new Thread(() -> mpc.consume(), "消费者-" + i).start();
}
}
}
模式2:同步屏障(Barrier)
等待所有线程到达某个点后再继续执行:
public class SimpleBarrier {
private final int parties;
private int count;
private final Object lock = new Object();
public SimpleBarrier(int parties) {
this.parties = parties;
this.count = parties;
}
public void await() throws InterruptedException {
synchronized (lock) {
count--;
if (count > 0) {
// 还有线程没到,等待
System.out.println(Thread.currentThread().getName() +
" 等待,还差 " + count + " 个线程");
lock.wait();
} else {
// 最后一个线程到达,唤醒所有等待的线程
System.out.println(Thread.currentThread().getName() +
" 是最后一个,唤醒所有线程");
count = parties; // 重置计数
lock.notifyAll();
}
}
}
public static void main(String[] args) {
SimpleBarrier barrier = new SimpleBarrier(3);
for (int i = 1; i <= 3; i++) {
int id = i;
new Thread(() -> {
try {
System.out.println("线程-" + id + " 准备中...");
Thread.sleep(id * 1000); // 模拟准备时间不同
System.out.println("线程-" + id + " 到达屏障");
barrier.await(); // 等待其他线程
System.out.println("线程-" + id + " 通过屏障,继续执行");
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "线程-" + id).start();
}
}
}
输出:
线程-1 准备中...
线程-2 准备中...
线程-3 准备中...
线程-1 到达屏障
线程-1 等待,还差 2 个线程
线程-2 到达屏障
线程-2 等待,还差 1 个线程
线程-3 到达屏障
线程-3 是最后一个,唤醒所有线程
线程-3 通过屏障,继续执行
线程-1 通过屏障,继续执行
线程-2 通过屏障,继续执行
模式3:顺序执行
确保线程按特定顺序执行:
public class SequentialExecution {
private int flag = 1;
private final Object lock = new Object();
public void printA() {
for (int i = 0; i < 10; i++) {
synchronized (lock) {
while (flag != 1) {
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.print("A");
flag = 2;
lock.notifyAll();
}
}
}
public void printB() {
for (int i = 0; i < 10; i++) {
synchronized (lock) {
while (flag != 2) {
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.print("B");
flag = 3;
lock.notifyAll();
}
}
}
public void printC() {
for (int i = 0; i < 10; i++) {
synchronized (lock) {
while (flag != 3) {
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("C");
flag = 1;
lock.notifyAll();
}
}
}
public static void main(String[] args) {
SequentialExecution se = new SequentialExecution();
new Thread(() -> se.printA()).start();
new Thread(() -> se.printB()).start();
new Thread(() -> se.printC()).start();
}
}
输出:
ABC
ABC
ABC
ABC
ABC
ABC
ABC
ABC
ABC
ABC
常见陷阱与最佳实践
陷阱1:不在synchronized块中调用
// ❌ 错误
lock.wait(); // IllegalMonitorStateException
// ✅ 正确
synchronized (lock) {
lock.wait();
}
陷阱2:使用if而不是while
// ❌ 错误:虚假唤醒会导致问题
synchronized (lock) {
if (!condition) {
lock.wait();
}
doSomething(); // 可能condition还是false
}
// ✅ 正确:使用while循环
synchronized (lock) {
while (!condition) {
lock.wait();
}
doSomething(); // 确保condition为true
}
陷阱3:notify()唤醒了错误的线程
// ❌ 问题:notify()随机唤醒一个线程
synchronized (lock) {
lock.notify(); // 可能唤醒了生产者而不是消费者
}
// ✅ 解决方案1:使用notifyAll()
synchronized (lock) {
lock.notifyAll(); // 唤醒所有线程,让它们自己判断
}
// ✅ 解决方案2:使用Condition(见ReentrantLock)
Lock lock = new ReentrantLock();
Condition notEmpty = lock.newCondition();
Condition notFull = lock.newCondition();
// 可以精确唤醒特定条件的线程
陷阱4:忘记处理InterruptedException
// ❌ 错误:吞掉异常
synchronized (lock) {
try {
lock.wait();
} catch (InterruptedException e) {
// 什么都不做
}
}
// ✅ 正确:恢复中断状态或向上传播
synchronized (lock) {
try {
lock.wait();
} catch (InterruptedException e) {
Thread.currentThread().interrupt(); // 恢复中断状态
// 或者重新抛出
}
}
最佳实践
1. 总是使用while循环
synchronized (lock) {
while (!condition) {
lock.wait();
}
// 执行操作
}
2. 尽量使用notifyAll()
// 除非确定只需要唤醒一个线程,否则使用notifyAll()
synchronized (lock) {
lock.notifyAll(); // 更安全
}
3. 条件谓词要简单明确
// ✅ 好:条件明确
while (queue.isEmpty()) {
lock.wait();
}
// ❌ 不好:条件复杂
while (queue.size() < MIN_SIZE && !shutdown && time < deadline) {
lock.wait(); // 太复杂,难以维护
}
4. 考虑使用更高级的工具
// 如果wait/notify太复杂,考虑使用:
// - BlockingQueue(生产者-消费者)
// - CountDownLatch(等待多个线程)
// - CyclicBarrier(同步屏障)
// - Semaphore(资源控制)
// - ReentrantLock + Condition(更灵活的等待通知)
// 例如:使用BlockingQueue简化生产者-消费者
BlockingQueue<Integer> queue = new ArrayBlockingQueue<>(10);
// 生产
queue.put(item); // 自动等待队列有空间
// 消费
int item = queue.take(); // 自动等待队列有元素
🎯 知识点总结
wait/notify核心要点
| 方法 | 作用 | 注意事项 |
|---|---|---|
wait() |
释放锁并等待 | 必须在synchronized块中 |
notify() |
唤醒一个等待的线程 | 不立即释放锁 |
notifyAll() |
唤醒所有等待的线程 | 更安全,推荐使用 |
标准模式
synchronized (lock) {
while (!condition) {
lock.wait();
}
// 执行操作
condition = false;
lock.notifyAll();
}
关键点
- ✅ 必须在synchronized块中调用
- ✅ 使用while而不是if检查条件
- ✅ 优先使用notifyAll()
- ✅ 正确处理InterruptedException
- ✅ 考虑使用更高级的并发工具
💡 常见面试题
Q1:wait()和sleep()的区别?
答:
- wait()是Object的方法,sleep()是Thread的静态方法
- wait()会释放锁,sleep()不释放锁
- wait()必须在synchronized块中调用,sleep()可以在任何地方调用
- wait()需要notify()唤醒,sleep()时间到自动醒来
Q2:为什么wait/notify必须在synchronized块中?
答:因为wait/notify操作的是对象的Monitor,而只有获得了Monitor的ownership(即获得了锁)才能操作Monitor。如果不在synchronized块中调用,会抛出IllegalMonitorStateException。
Q3:为什么要用while而不是if检查条件?
答:因为存在虚假唤醒(Spurious Wakeup)。线程可能在没有被notify的情况下从wait()返回。使用while循环可以在被唤醒后重新检查条件,确保条件满足才继续执行。
Q4:notify()和notifyAll()有什么区别?
答:
- notify():随机唤醒WaitSet中的一个线程
- notifyAll():唤醒WaitSet中的所有线程
- 推荐使用notifyAll(),因为更安全,可以避免唤醒错误的线程
Q5:调用notify()后会立即释放锁吗?
答:不会。调用notify()后,当前线程继续持有锁,直到synchronized块结束才释放锁。被唤醒的线程会从WaitSet移到EntryList,等待获取锁。
更多推荐



所有评论(0)