AQS 队列如何唤醒阻塞线程¶
一、线程怎么挂起¶
acquireQueued 里,前驱不是 head 时,线程挂起:
private static void selfInterrupt() {
Thread.currentThread().interrupt();
}
// 在 parkAndCheckInterrupt 里
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this); // 挂起当前线程
return Thread.interrupted();
}
LockSupport.park() 调用 Unsafe.park(),线程进入 WAITING 状态,不占 CPU。
二、怎么唤醒¶
锁释放时,release → unparkSuccessor(head):
private void unparkSuccessor(Node node) {
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
Node s = node.next;
if (s == null || s.waitStatus > 0) {
s = null;
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
if (s != null)
LockSupport.unpark(s.thread); // 唤醒后继
}
流程¶
- 找 head 的下一个有效 node。
LockSupport.unpark(thread)唤醒。- 被唤醒的线程从
park()返回,继续自旋 tryAcquire。
三、为什么从尾往前找¶
因为入队时 next 指针是后加的,可能读到 null。从 tail 往前找 prev 更安全。
四、被唤醒后做什么¶
final boolean acquireQueued(Node node, int arg) {
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) { // 前驱是 head,再抢一次
setHead(node);
p.next = null;
return interrupted;
}
// 不该抢,继续 park
}
}
被唤醒后,检查前驱是不是 head: - 是:自己成为队列头,拿到锁。 - 不是:继续 park。
五、为什么不直接唤醒¶
锁释放后,可能有多个线程在等,但只唤醒一个(独占锁)。让队列里第一个去抢,抢到了就往下传。
park/unpark
LockSupport.park():挂起当前线程。LockSupport.unpark(thread):唤醒指定线程。- 基于 Unsafe.park/unpark,不依赖 Object 的 wait/notify。