等待与唤醒机制——wait/notify、Condition 与 LockSupport 对照
并发里所有“等待/唤醒”问题,最终都绕不开三套机制:
Object.wait/notify/notifyAll(监视器机制,配合synchronized)Condition.await/signal/signalAll(显式锁配套)LockSupport.park/unpark(更底层,AQS/FutureTask/线程池广泛使用)
写清楚它们的差异,很多“信号丢失/虚假唤醒/死等”的问题会一下子明朗。
1. 对比速查表
| 机制 | 等待条件 | 唤醒方式 | 是否必须持有锁 | 是否可能虚假唤醒 | 典型使用处 |
|---|---|---|---|---|---|
wait/notify |
对象 monitor 的 wait-set | notify/notifyAll |
是(必须在 synchronized 内) | 是 | 传统生产者-消费者、synchronized 协作 |
Condition |
条件队列(每个 Condition 一个队列) | signal/signalAll |
是(必须持有关联 Lock) | 是 | ReentrantLock 协作、多条件队列 |
LockSupport |
线程的“permit” | unpark(thread) |
否 | 会(park 可能无理由返回) | AQS、FutureTask、线程池阻塞 |
2. wait/notify:监视器上的等待集合
2.1 关键规则
- 必须在
synchronized(obj)内调用obj.wait()/obj.notify() wait()会:- 释放 monitor
- 把线程加入 obj 的等待集合
- 被唤醒后重新竞争 monitor
2.2 正确姿势:永远用 while
1 | synchronized (lock) { |
原因:
- 虚假唤醒:线程可能在没有收到通知时返回
- 竞争唤醒:notifyAll 后只有一个线程能拿到资源,其余线程需要继续等
2.3 notify vs notifyAll
notify():只随机唤醒一个等待线程,容易造成“唤醒了不该唤醒的人”(条件不满足又继续睡)notifyAll():唤醒全部,依靠 while 自旋检查条件;更安全但唤醒风暴更大
3. Condition:更可控的等待/唤醒
Condition 的最大价值是:
- 一个 Lock 可以派生多个 Condition
- 每个 Condition 有自己的等待队列
这让“多条件协作”更清晰:
- notEmpty
- notFull
- hasResource
3.1 模板
1 | ReentrantLock lock = new ReentrantLock(); |
3.2 工程易错点
await()抛InterruptedException:要决定是“吞掉并继续”还是“回填标志并退出”(通常建议回填并退出)signal()一定要在持锁状态下调用(否则时序很难保证)
4. LockSupport:permit 语义(不会丢信号)
LockSupport 可以理解成:
- 每个线程有一个
permit(0 或 1) park():如果 permit=0 就阻塞;如果 permit=1 就消费 permit 并立即返回unpark(t):把 t 的 permit 置为 1(如果已经是 1 就保持 1)
关键结论:
- unpark 可以先于 park 调用,信号不会丢(permit 会被记住)
这也是它被 AQS 选为底层工具的重要原因。
4.1 但为什么还说可能“无理由返回”
park()允许虚假返回:- 中断
- 其它原因
所以用 park 的上层实现(AQS/FutureTask)都会在循环里反复检查条件。
5. “信号丢失”与“死等”常见成因
- 用
if代替while(虚假唤醒直接穿透) - 条件检查与入队/等待不是原子序列(检查完条件后再 wait,期间错过 notify)
- 多条件混用一个 wait-set(notify 唤醒了不该唤醒的线程)
规避建议:
- 用 Condition 拆分条件队列
- 用 while 守护条件
- 用更底层的并发结构(BlockingQueue)替代手写 wait/notify
6. 总结:怎么选
synchronized协作:用wait/notifyAll,并坚持 whileReentrantLock:用Condition(多条件更清晰)- 写同步器/框架代码:用
LockSupport(permit 语义更适合)
面试题 / Checklist
- 为什么
wait()/notify()必须在synchronized里调用? - 为什么必须用
while而不是if来守护条件? notify()与notifyAll()的取舍点是什么?Condition相比wait/notify解决了什么问题(多条件队列/精准唤醒)?LockSupport的 permit 语义是什么?为什么unpark可以先于park?- 什么是“信号丢失”?在手写等待/唤醒时如何避免?
