java中ReentrantLock使用公平锁相关问题
在 AQS 的锁里应该用 Condition.await() / signal() 那么,这里使用了Condition.await()的线程,挂到哪里去?CLH 队列尾部吗?
好问题 👌,你已经把 AQS 的同步队列 (CLH queue) 和 Condition 机制 联系起来了。这里要特别小心理解,因为 Condition.await()
的线程不是直接挂到 CLH 同步队列里,而是挂在 Condition 的等待队列 (Condition Queue)。
1️⃣ AQS 中有两种队列
-
同步队列 (Sync Queue / CLH 队列)
- 存放那些 因为抢不到锁而阻塞的线程。
- AQS 的核心:
LockSupport.park/unpark
挂起和唤醒都靠它。
-
条件队列 (Condition Queue)
- 每个
Condition
对象独立维护的一个单向链表队列。 - 存放那些调用了
Condition.await()
的线程。 - 它们不是立即去抢锁,而是等
signal()
把它们“转移”到同步队列后,才有机会竞争锁。
- 每个
2️⃣ Condition.await()
的执行过程
当线程 已经持有锁,调用 await()
时会发生:
-
释放锁(这点跟
Object.wait()
类似)state
归零,锁完全释放。- 方便其它线程拿到锁。
-
加入 Condition 队列
- 当前线程被封装为一个 Node,加入 Condition Queue。
-
挂起线程
- 调用
LockSupport.park()
进入阻塞状态。
- 调用
👉 此时线程不在同步队列,而在 Condition Queue。
3️⃣ Condition.signal()
的执行过程
当别的线程调用 signal()
:
-
从 Condition Queue 头部取出一个 Node(等待线程)。
-
把它从 Condition Queue 转移到 CLH 同步队列。
- 转移动作是 AQS 内部的核心逻辑:
transferForSignal()
。
- 转移动作是 AQS 内部的核心逻辑:
-
被转移的线程依然阻塞着,直到它在同步队列里排到队首,被唤醒后重新竞争锁。
4️⃣ ✅ 总结
-
Condition.await()
的线程 不会立即进入 CLH 队列,而是进入 Condition Queue。 -
只有在
signal()
或signalAll()
时,才会被转移到 CLH 队列,等待重新竞争锁。 -
因此:
- CLH 队列 = 锁竞争阻塞队列
- Condition 队列 = 等待条件的线程队列
-
最终线程一定会回到 CLH 队列,因为想继续执行必须重新抢锁。