回答
当线程获取锁失败后,会加入到 CLH 同步队列中,同时调用 LockSupport.park()
阻塞。当线程将锁释放完毕后,需要唤醒后继节点,调用 LockSupport.unpark()
唤醒线程。
LockSupport
用来创建锁和其他同步类的基本线程阻塞原语,相比 wait()
和 notify()
提供了一种非常灵活的线程阻塞和唤醒机制。它提供提供了两个方法:
park()
:用于阻塞当前线程。unpark()
:用于唤醒一个被park()
阻塞的线程。
LockSupport
使用一种名为“许可证”的概念。每个线程都有一个与之关联的许可证(最多一个)。park()
会消耗掉这个许可证(如果存在的话),并在没有许可证的情况下阻塞线程。而 unpark()
则提供一个许可证。
详细
AQS 实现线程阻塞
在线程获取锁失败后,会被包装为一个 Node 节点,添加到 CLH 同步队列中,并且该节点会自旋式地不断检测是否可以获取锁,在获取锁的过程中,需要判断当前线程是否需要被阻塞:
shouldParkAfterFailedAcquire()
:线程需要阻塞,返回 true,否则返回 false。
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus; // 前驱节点状态
if (ws == Node.SIGNAL)
/*
* 前驱节点状态为 SIGNAL,那么当前线程肯定需要等待
*/
return true;
if (ws > 0) {
/*
* waitStatus > 0,为 CANCELLED,表示线程被中断或者超时取消了,需要从 CLH 队列中移除
*/
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
/*
* 前驱节点为 CONDITION 或者 PROPAGATE,则将前驱节点设置为 SIGNAL
*/
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
从这里可以看出,只有当前驱节点为 SIGNAL 状态时,当前线程才会被阻塞。
parkAndCheckInterrupt()
:阻塞线程并检查是否被中断
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
调用 LockSupport.park(this)
阻塞当前线程。