目录一、先搞懂基础二、核心原因3 点1. 防止 丢失唤醒Lost Wake-Up最关键错误时序无锁场景加锁后同步块时序被原子化2. JVM 语法强制必须持有对象监视器3. 保证等待 / 唤醒逻辑的线程安全三、补充最佳实践面试常考面试极简背诵版一句话结论为了规避「丢失唤醒」并发 bug同时保证线程安全、语义合法JVM 强制要求必须持有对象锁才能调用。一、先搞懂基础wait()、notify()、notifyAll()是Object 类方法作用于对象监视器锁。调用obj.wait()当前线程释放 obj 锁进入该对象的等待集阻塞等待被唤醒。调用obj.notify()随机唤醒等待集中一个线程notifyAll()唤醒全部。二、核心原因3 点1. 防止丢失唤醒Lost Wake-Up最关键这是设计层面的根本原因先看不在同步块会出现的致命问题错误时序无锁场景假设业务逻辑条件不满足 → 等待条件满足 → 唤醒线程 A判断条件不成立正要执行wait()CPU 切换到线程 B线程 B修改数据条件成立执行notify()唤醒 →此时线程 A 还没进入等待队列这次唤醒直接作废CPU 切回线程 A继续执行wait()→ 线程 A 永久阻塞永远没人再唤醒它丢失唤醒加锁后同步块时序被原子化锁保证「条件判断 wait ()」是原子操作线程 A 获取锁 → 判断条件不成立直接执行wait()原子完成中间不会被打断线程 B 必须等 A 释放锁后才能进入、修改条件、notify 彻底杜绝「先唤醒、后等待」的丢失唤醒问题。2. JVM 语法强制必须持有对象监视器JVM 规范规定调用wait()/notify()时当前线程必须持有该 Object 的内置锁。如果没有持有锁直接调用会立刻抛出java.lang.IllegalMonitorStateException原因wait()语义是 **“拿着锁主动放弃锁并等待”**没有锁就不存在「放弃锁」这个动作语义不成立。3. 保证等待 / 唤醒逻辑的线程安全等待一般都伴随共享变量条件判断比如队列空 / 满、状态标志共享变量本身就存在并发竞争必须加锁保证可见性、原子性同步块天然保证变量状态在线程间及时可见避免因指令重排、内存可见性导致逻辑错乱。三、补充最佳实践面试常考wait()一定要放在while循环里防止虚假唤醒线程可能在无 notify 时被意外唤醒synchronized (obj) { while (条件不满足) { obj.wait(); } // 执行业务 }notify()/notifyAll()也必须在同一个对象锁的同步块中。优先用notifyAll()多数场景下更安全。面试极简背诵版防止丢失唤醒锁让「条件判断 wait」原子执行避免唤醒先于等待发生JVM 强制校验调用这两个方法必须持有对象监视器否则抛IllegalMonitorStateException保证线程安全同步锁保障条件变量的可见性与原子性。
为什么 wait() / notify() 必须在同步代码块 / 同步方法中调用
目录一、先搞懂基础二、核心原因3 点1. 防止 丢失唤醒Lost Wake-Up最关键错误时序无锁场景加锁后同步块时序被原子化2. JVM 语法强制必须持有对象监视器3. 保证等待 / 唤醒逻辑的线程安全三、补充最佳实践面试常考面试极简背诵版一句话结论为了规避「丢失唤醒」并发 bug同时保证线程安全、语义合法JVM 强制要求必须持有对象锁才能调用。一、先搞懂基础wait()、notify()、notifyAll()是Object 类方法作用于对象监视器锁。调用obj.wait()当前线程释放 obj 锁进入该对象的等待集阻塞等待被唤醒。调用obj.notify()随机唤醒等待集中一个线程notifyAll()唤醒全部。二、核心原因3 点1. 防止丢失唤醒Lost Wake-Up最关键这是设计层面的根本原因先看不在同步块会出现的致命问题错误时序无锁场景假设业务逻辑条件不满足 → 等待条件满足 → 唤醒线程 A判断条件不成立正要执行wait()CPU 切换到线程 B线程 B修改数据条件成立执行notify()唤醒 →此时线程 A 还没进入等待队列这次唤醒直接作废CPU 切回线程 A继续执行wait()→ 线程 A 永久阻塞永远没人再唤醒它丢失唤醒加锁后同步块时序被原子化锁保证「条件判断 wait ()」是原子操作线程 A 获取锁 → 判断条件不成立直接执行wait()原子完成中间不会被打断线程 B 必须等 A 释放锁后才能进入、修改条件、notify 彻底杜绝「先唤醒、后等待」的丢失唤醒问题。2. JVM 语法强制必须持有对象监视器JVM 规范规定调用wait()/notify()时当前线程必须持有该 Object 的内置锁。如果没有持有锁直接调用会立刻抛出java.lang.IllegalMonitorStateException原因wait()语义是 **“拿着锁主动放弃锁并等待”**没有锁就不存在「放弃锁」这个动作语义不成立。3. 保证等待 / 唤醒逻辑的线程安全等待一般都伴随共享变量条件判断比如队列空 / 满、状态标志共享变量本身就存在并发竞争必须加锁保证可见性、原子性同步块天然保证变量状态在线程间及时可见避免因指令重排、内存可见性导致逻辑错乱。三、补充最佳实践面试常考wait()一定要放在while循环里防止虚假唤醒线程可能在无 notify 时被意外唤醒synchronized (obj) { while (条件不满足) { obj.wait(); } // 执行业务 }notify()/notifyAll()也必须在同一个对象锁的同步块中。优先用notifyAll()多数场景下更安全。面试极简背诵版防止丢失唤醒锁让「条件判断 wait」原子执行避免唤醒先于等待发生JVM 强制校验调用这两个方法必须持有对象监视器否则抛IllegalMonitorStateException保证线程安全同步锁保障条件变量的可见性与原子性。