await()时线程先通过fullyRelease()彻底释放锁并退出同步队列,再以CONDITION状态加入Condition等待队列;signal()则将等待队列头节点CAS修改状态后插入同步队列并唤醒。
await() 时线程怎么从同步队列“消失”,又进到等待队列
调用
前,线程一定已持有锁(即已在 AQS 同步队列中成功 acquire),否则会抛
。真正释放锁的动作发生在
—— 它把当前线程持有的全部 state(比如可重入次数)一次性归零,并唤醒同步队列中下一个阻塞节点(如果存在)。此时线程已完全退出同步队列,也不再持有锁。
接着,该线程被封装成一个
,以
状态插入到 ConditionObject 的单向链表尾部(
),这个链表就是「等待队列」。注意:插入过程不需 CAS,因为调用
必须在 lock 持有状态下执行,天然串行。
常见错误现象:
—— 忘了在
之后调用
关键点:
是彻底释放,不是“暂时让出”,所以其他线程能立刻抢到锁
性能影响:频繁 await/signal 会引发大量节点在两个队列间迁移,增加 CAS 和链表操作开销
signal() 怎么把节点从等待队列“搬”回同步队列
不唤醒线程本身,只做一件事:把等待队列头节点(
)用 CAS 改为
状态(即脱离 CONDITION),然后调用
把它插进 AQS 同步队列尾部。这个过程由
完成。
一旦入队成功,就调用
唤醒线程。被唤醒的线程会从
返回,接着执行
判断——此时它已在同步队列中,循环退出,进入
竞争锁。
容易踩的坑:
调用后线程不会立即执行,仍要排队等锁;若此时锁被其他线程抢先获取,该线程继续阻塞在同步队列中
参数差异:
只搬头节点,
遍历整个等待队列,逐个
虚假唤醒防护:即使没被 signal,线程也可能因系统信号被 park 唤醒,所以
外层必须用
而非
检查条件
中断发生时,节点转移逻辑会绕过 signal 直接进同步队列
线程在
中被中断,
会返回非 0 值(如
或
),并立即跳出等待循环。随后直接调用
—— 这个方法本质就是让该节点像普通竞争者一样进同步队列排队拿锁。
也就是说:**中断 ≈ 隐式 signal**,但不经过等待队列头部,而是由当前线程自己触发转移。这也解释了为什么中断后仍需重新 acquire:它没被 signal,锁资源还被别人占着。
典型错误写法:在
外层 catch
后直接 return,导致锁未重新获取、资源处于不一致状态
兼容性注意:
会忽略中断,直到被 signal 才返回,内部不调用
调试线索:如果发现线程在没调用
的情况下突然开始竞争锁,优先检查是否被中断
多个 Condition 共享一把锁,但等待队列完全隔离
每个
实例都有独立的
/
,互不干扰。比如
的
每次都 new 一个新对象,所以
和
的线程分别在不同链表里。
这种设计让「多条件等待」成为可能:生产者只
,消费者只
,彼此不会误唤醒。而 Object 的
只有一个隐式队列,只能靠
撑场面。
实际陷阱:误把不同 Condition 的
当作通用唤醒,结果目标线程根本不在那个等待队列里,永远等下去
内存开销:每个 Condition 对象本身很轻量,但每个等待中的线程都会持有一个
,长期堆积会增大队列长度
调试建议:用 jstack 查线程堆栈时,看到
只能说明在同步队列;要看是否在等待队列,得结合业务逻辑和 Condition 使用位置判断
真正难啃的是节点状态切换的原子性边界:CONDITION → 0 的 CAS 成功,不代表 enq 就一定成功;enq 成功,也不代表 unpark 就立刻生效。这些间隙正是并发复杂性的根源,也是你读源码时最该盯住的几行。
await()IllegalMonitorStateExceptionfullyRelease(node)NodeNode.CONDITIONaddConditionWaiter()await()IllegalMonitorStateExceptionlock.lock()await()fullyRelease()signal()firstWaiter0enq(node)transferForSignal()LockSupport.unpark(node.thread)LockSupport.park(this)isOnSyncQueue(node)acquireQueued()signal()signal()signalAll()transferForSignal()await()whileifawait()checkInterruptWhileWaiting()THROW_IEREINTERRUPTacquireQueued(node, savedState)await()InterruptedExceptionawaitUninterruptibly()checkInterruptWhileWaiting()signal()ConditionObjectfirstWaiterlastWaiterReentrantLocknewCondition()c1.await()c2.await()signal(notFull)signal(notEmpty)wait()/notify()notifyAll()signal()Nodeparking to wait for <0x...>