本文参考文章:万字超强图文讲解AQS以及ReentrantLock应用
1. CLH队列的头结点和尾结点都是虚拟节点,头结点可以理解为哨兵节点。并且为了节省内存都是lazy initialized. 并且头结点如果存在,可以保证部位CANCELLED状态
2. 在doAcquireNanos(int args, long nanosTimeout)方法中,也就是具有超时功能的获取锁的过程中,并不是一开始就park住线程的,有一个自旋阈值,如果超过了在挂起,因为如果时间没有超过1000 nanosseconds的话没必要进行挂起和唤醒操作,不如直接让线程进入下一次循环。这个思想跟JDK中的自旋锁很像,自旋锁适用于锁保护的临界区很小的情况,临界区很小的话,锁占用的时间很短。自旋等待不能替代阻塞,虽然他可以避免线程切换带来的开销,但是它占用CPU处理器的时间,如果持有锁的线程很快就释放了锁,那么自旋的效率就非常好。
private boolean doAcquireNanos(int arg, long nanosTimeout)
throws InterruptedException {
if (nanosTimeout <= 0L)
return false;
final long deadline = System.nanoTime() + nanosTimeout;
final Node node = addWaiter(Node.EXCLUSIVE);
boolean failed = true;
try {
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return true;
}
nanosTimeout = deadline - System.nanoTime();
if (nanosTimeout <= 0L)
return false;
if (shouldParkAfterFailedAcquire(p, node) &&
nanosTimeout > spinForTimeoutThreshold)
LockSupport.parkNanos(this, nanosTimeout);
if (Thread.interrupted())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
/**
* The number of nanoseconds for which it is faster to spin
* rather than to use timed park. A rough estimate suffices
* to improve responsiveness with very short timeouts.
*/
static final long spinForTimeoutThreshold = 1000L;
3. AQS中的conditionObject类,里面有两个成员变量,firstWaiter, lastWaiter。 在await方法中会释放同步状态,所以说condition.await会释放锁,但是LockSupport.park并不会释放锁,类似于Sleep一样,不会释放锁,只会响应中断。
public final void await() throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
Node node = addConditionWaiter();
int savedState = fullyRelease(node);
int interruptMode = 0;
while (!isOnSyncQueue(node)) {
LockSupport.park(this);
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null) // clean up if cancelled
unlinkCancelledWaiters();
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}
addWaiter方法是构建一个自己的同步队列,这个队列是单向队列,为什么是单向的,是因为这里不涉及到竞争锁,只是做一个条件等待队列。在Lock中定义多个条件,每个条件都会对应一个条件等待队列。
private Node addConditionWaiter() {
Node t = lastWaiter;
// If lastWaiter is cancelled, clean out.
if (t != null && t.waitStatus != Node.CONDITION) {
unlinkCancelledWaiters();
t = lastWaiter;
}
Node node = new Node(Thread.currentThread(), Node.CONDITION);
if (t == null)
firstWaiter = node;
else
t.nextWaiter = node;
lastWaiter = node;
return node;
}
关于wait的使用是有范式的,必须在lock.lock中使用,类似于wait的使用要在synchronized中一样,具体原因可以查看文章synchronized底层原理。从条件等待队列移到同步队列是有时间差的,有时间差就会有公平和非公平的问题。
公平锁就是判断队列是否还有前驱节点存在,如果没有才能获取锁,而非公平锁不管这个事,直接去获取。
4. AQS里面用到了模板方法的设计模式,一种经典的设计模式。把关于CLH队列的操作封装起来。