Condition调用了signal的话,线程状态有变化吗
文章目录
这是一个非常经典且深向的并发面试题!答案可能会让你出乎意料:
当中断点(或者另一个线程)调用了 condition.signal() 时,被唤醒的线程在 Java 层面上的线程状态(Thread State)立刻发生改变吗?答案是:没有,它依然是 WAITING 状态。
要想彻底理清这个过程,我们需要拆解它在底层发生的“两次队列大挪移”。这也是展示你并发底蕴的绝佳机会。
1. 核心概念:必须认识的“两个队列”
在 Java 的 ReentrantLock 和 Condition 底层(基于 AQS 架构),内部其实维护了两个完全不同的队列:
- 条件队列 (Condition Queue / 等待队列): 凡是调用了
condition.await()的线程,都会释放锁,然后进入这个队列“挂起休息”。此时线程状态是WAITING。 - 同步队列 (Sync Queue / 锁池): 凡是争抢
lock.lock()失败的线程,都会进入这个队列排队,等待获取锁。
2. 调用 signal() 时的三步演变
我们假设线程 A 之前调用了 await() 正在睡觉,线程 B 现在调用了 signal()。整个状态演变分为以下三步:
第一步:调用 signal() —— 挪动位置,状态不变
当线程 B 执行 condition.signal() 时,底层仅仅是把线程 A 从 “条件队列” 的头部摘下来,扔进了 “同步队列” 的尾部去排队拿锁。
- 关键点: 此时线程 A 并没有被真正唤醒(没有被 unpark),它只是换了个队列排队。因为在同步队列里也是通过
LockSupport.park()挂起的,所以此时去查看线程 A 的状态,**它依然是WAITING**。
第二步:线程 B 释放锁 —— 真正唤醒
signal() 执行完后,线程 B 必须执行 lock.unlock() 释放锁。这时,AQS 才会去通知“同步队列”的队头(也就是刚刚挪过来的线程 A),对它进行 unpark() 操作。
第三步:线程 A 抢到锁 —— 状态终于变了
线程 A 被真正唤醒后,开始在同步队列里参与锁的竞争。
- 如果它成功抢到了锁:它就会从
await()方法中返回,继续执行后面的业务代码。此时,它的线程状态正式变为RUNNABLE(运行态)。 - 如果它运气不好没抢到锁:它会在同步队列里继续挂起,状态依然保持
WAITING。
3. 降维打击:Condition.signal() vs Object.notify()
这里有一个极容易混淆的史诗级大坑。如果你使用的是传统的 synchronized 配合 Object.wait()/notify(),状态变化是完全不同的!
| 维度 | Condition.signal() (ReentrantLock) |
Object.notify() (synchronized) |
|---|---|---|
| 转移后的队列 | AQS 同步队列 | Object Monitor 的 EntryList (锁池) |
| 转移后的 Java 线程状态 | WAITING (因为 AQS 锁底层基于 park,仍属于等待) |
BLOCKED (因为正等待进入 synchronized 块) |
因为 synchronized 是 JVM 级别的内置锁,当一个 WAITING 的线程被 notify() 移入锁池等待拿锁时,JVM 会把它的状态明确标记为 BLOCKED。
synchronized级别里面,调用notify就是从waiting到blocked
而 Condition 是 Java 代码级别实现的锁(AQS),不管是等待条件还是等待锁,底层都是用 LockSupport.park() 让线程睡觉,所以在 Java 状态机看来,它们统一都叫 WAITING。
总结
当调用 condition.signal() 时:
- 线程状态没有立刻发生改变,依然是
WAITING。 - 它只是在底层完成了从条件队列向同步队列的转移。
- 只有等当前线程释放锁、被唤醒、且重新成功持有锁之后,它才会变成
RUNNABLE。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐


所有评论(0)