这是一个非常经典且深向的并发面试题!答案可能会让你出乎意料:

当中断点(或者另一个线程)调用了 condition.signal() 时,被唤醒的线程在 Java 层面上的线程状态(Thread State)立刻发生改变吗?答案是:没有,它依然是 WAITING 状态。

要想彻底理清这个过程,我们需要拆解它在底层发生的“两次队列大挪移”。这也是展示你并发底蕴的绝佳机会。


1. 核心概念:必须认识的“两个队列”

在 Java 的 ReentrantLockCondition 底层(基于 AQS 架构),内部其实维护了两个完全不同的队列:

  1. 条件队列 (Condition Queue / 等待队列): 凡是调用了 condition.await() 的线程,都会释放锁,然后进入这个队列“挂起休息”。此时线程状态是 WAITING
  2. 同步队列 (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() 时:

  1. 线程状态没有立刻发生改变,依然是 WAITING
  2. 它只是在底层完成了从条件队列向同步队列的转移
  3. 只有等当前线程释放锁、被唤醒、且重新成功持有锁之后,它才会变成 RUNNABLE
Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐