当前位置: 首页 > news >正文

wait和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("服务员:等待食物...");// 不停地检查,浪费CPUwhile (!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();}// 被唤醒或超时后继续执行
}

两种退出方式

  1. 被notify/notifyAll唤醒
  2. 超时自动唤醒

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先waitt2.start();}
}

输出

t1: 开始等待
t2: 准备唤醒t1
t2: 已调用notify,但还持有锁
(等待2秒)
t2: 即将释放锁
t2: 已释放锁
t1: 被唤醒,继续执行  ← t2释放锁后,t1才能继续

wait/notify原理

wait()方法完整流程图

NO
YES
notify/notifyAll
timeout超时
interrupt中断
NO
YES
线程调用wait方法
是否持有锁?
抛出IllegalMonitorStateException
释放当前持有的锁
Owner = null
当前线程进入WaitSet
线程状态: WAITING
阻塞等待...
被唤醒?
从WaitSet移到EntryList
抛出InterruptedException
竞争锁成功?
在EntryList中等待
Owner = 当前线程
从wait方法返回
继续执行后续代码

notify()方法完整流程图

NO
YES
YES空
NO非空
线程调用notify方法
是否持有锁?
抛出IllegalMonitorStateException
WaitSet是否为空?
什么也不做
选择WaitSet中的一个线程
将该线程移到EntryList
当前线程继续持有锁
执行完synchronized块
释放锁
EntryList中的线程竞争锁

notifyAll()方法流程图

NO
YES
YES空
NO非空
线程调用notifyAll方法
是否持有锁?
抛出IllegalMonitorStateException
WaitSet是否为空?
什么也不做
将WaitSet中的所有线程
全部移到EntryList
当前线程继续持有锁
执行完synchronized块
释放锁
EntryList中所有线程竞争锁
只有一个线程能获得锁
其他线程继续在EntryList中等待

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();
}

关键点

  1. 必须在synchronized块中调用
  2. 使用while而不是if检查条件
  3. 优先使用notifyAll()
  4. 正确处理InterruptedException
  5. 考虑使用更高级的并发工具

💡 常见面试题

Q1:wait()和sleep()的区别?

  1. wait()是Object的方法,sleep()是Thread的静态方法
  2. wait()会释放锁,sleep()不释放锁
  3. wait()必须在synchronized块中调用,sleep()可以在任何地方调用
  4. 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,等待获取锁。

http://www.dtcms.com/a/529316.html

相关文章:

  • 网站开发文档需求撰写word营销型网站建站系统
  • wordpress order插件seo实训报告
  • 南宁建设厅网站是什么品牌网络市场环境调研报告
  • 做外贸需要做网站吗电子商务网站建设读书笔记
  • Linux17 进程间的通信 消息队列
  • 从WSL安装到初始化buildozer全过程~
  • 点击网站排名西南网架公司
  • 专做宠物的网站注册一个5000万空壳公司要多少钱
  • 长春火车站进站需要核酸检测吗豆瓣 wordpress
  • 【Java 序列化 (Serialization)】
  • STM32H743-ARM例程30-Modbus
  • ps网站导航怎么做wordpress 主题详解
  • 网站建设全网推广小程序网站制作app排行榜前十名
  • 正规网站建设多少费用深圳品牌设计公司哪家好
  • Product Hunt 每日热榜 | 2025-10-25
  • Java实用工具库深度解析:从生产力到艺术性
  • 全网营销网站建设特点南山出名的互联网公司
  • 计算机组成原理C,存储器容量计算地址线和数据线
  • 连云港建设局官方网站模板大全免费
  • 建设项目经济评价网站青岛公司网站建设价格
  • 重庆网站seo营销模板做网站怎么挣钱
  • 软件设计师知识点总结:软件工程
  • 智慧校园建设方案-3PPT(44页)
  • Neovim下载安装图解(附安装包,适合新手)
  • 做网站卖房写标题百度推广 网站备案
  • Grok、Claude、ChatGPT、Gemini模型适用场景比较
  • PHP网站开发涉及的工具有哪些中国建设集团门户网站
  • 网站开发 合同范本竞价托管
  • tkinter中各组件的属性设置及应用举例
  • 如何做游戏试玩网站福田网站建设运营费用