condition信号丢失是设计使然而非bug,因signal不排队、不暂存,仅唤醒已在等待队列中的线程;典型场景为“先signal后await”,导致线程永久阻塞;防范依赖while循环检查条件、持锁调用signal、状态变更与signal同处临界区等编码规范。

Condition 的信号唤醒机制本身不保证信号不丢失,它依赖程序员严格遵守协作规范才能避免信号丢失。信号丢失不是 bug,而是设计使然——signal() 不排队、不暂存,只唤醒当前已在等待队列中的线程;如果目标线程还没调用 await(),信号就发出去了,那这个 signal 就彻底丢弃。
信号丢失的典型场景
最常见的信号丢失发生在“先 signal,后 await”:
- 线程 A 修改状态后立即调用
condition.signal(),但此时线程 B 还没执行到await()(比如刚获取锁、正检查条件) - 线程 B 后续进入
await(),但它等的是一个已经发过、且无人接收的信号 → 永远阻塞 - 这不是 Condition 的缺陷,而是它明确的设计哲学:信号是即时通知,不是消息队列
真正防止丢失的关键做法
靠的是编码约定,而不是机制自动兜底:
- 所有条件检查必须用 while 循环,而非 if。即使信号丢了,线程被其他原因唤醒(如虚假唤醒),也能重新判断条件是否真满足
- signal() 必须在持锁状态下调用,且最好紧邻状态变更之后、unlock() 之前。这样能最大程度确保等待线程已进入 await 或即将进入
- 状态变更和 signal() 要放在同一临界区内,避免中间被其他线程插队修改状态导致逻辑错乱
- 必要时可加轻量级同步辅助,比如用 volatile 标志位 + 自旋短等待,确保等待线程已就绪再 signal(仅调试或强一致性场景)
为什么不能靠 signalAll() 来“保险”?
signalAll() 并不解决信号丢失问题,它只是唤醒所有等待者:
- 如果没人 waiting,signalAll() 同样无效、无效果
- 它带来额外开销:所有被唤醒线程都要重新竞争锁、重新检查条件,可能引发惊群效应
- 它掩盖了设计缺陷——真正该做的是让 signal 和 await 在正确时机配对,而不是靠广播兜底
Condition 与 Object.wait/notify 的本质区别
Object 的 notify() 也存在同样丢失风险,但 Condition 的优势在于:
- 每个 Condition 有独立等待队列,signal() 只影响目标队列,不会误唤醒无关线程
- 配合 ReentrantLock 的显式锁管理,更容易控制 signal 的时机和上下文
- 支持超时 await(如 awaitNanos)、中断响应,给程序留出退路,降低卡死概率
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











