先上源码
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
if (ws == Node.SIGNAL)
return true;
if (ws > 0) {
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
先说下各函数作用:
- acquireQueued:不断自旋尝试获取同步状态(锁),获取不成功,则找安全点休息
- shouldParkAfterFailedAcquire:不断找安全点,如果已在安全点,返回true,否则找到安全点,并返回false
- parkAndCheckInterrupt:休眠线程并返回中断状态
可以看到shouldParkAfterFailedAcquire函数只有在安全点不变时才会返回true,而在其他情况均返回false,目的:
- 避免刚休眠就轮到自己获取锁:注意到:在acquireQueued中,shouldParkAfterFailedAcquire()与parkAndCheckInterrupt()连用,如果前者返回false,由于逻辑运算符的短路效应,所以parkAndCheckInterrupt就不会执行,线程就不会休眠。像是排队打饭,如果队伍动了一下,那就看看自己能不能打到饭,如果打饭的队伍动都没动,那就直接休息。
- 避免release唤醒后又再次休眠:在release时,头节点会唤醒后继节点中的线程,此时后继节点一直在自旋,自旋会执行if (p == head && tryAcquire(arg))和if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt());顺序可能会打乱,不过没关系,如果先执行前者,则 p == head 不为真,继续执行后者,在shouldParkAfterFailedAcquire找到安全点后返回false,从而避免让唤醒的线程再次休眠,而此时再进行一次自旋,p == head && tryAcquire(arg) 就会为真,从而成功获取锁,而此时由于线程已经唤醒,便可立即执行。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)