上篇博客讲了tryAcquire和tryRelease,这篇来看看AQS自身实现的doAcquire和doRelease
acquire
独占锁
AQS自身没有提供对doAcquireInterruptibly
和doAcquireNanos
的调用,交由子类使用,这俩都将同步队列节点入队操作放在了自己的方法内,而AQS自身对acquireQueued
的实现acquire
将这个操作从do方法中移了出来。
对比发现,三个方法的90%的内容是相同的,只是多了些中断检测和超时时间的检测。这些细微差异我们不重点讲,这里只以doAcquireInterruptibly
为例进行注释讲解
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
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);
}
}
//首先要明白,进入到这里之前,肯定是经过一次try方法,且不满足条件才会到这里来
//因此到这里是要进行阻塞等待获取的,但为了效率,在阻塞前会有机会再进行一次自旋再次try一下获取,如果还没有获取到就要准备阻塞了
private void doAcquireInterruptibly(int arg)
throws InterruptedException {
//这里会讲本线程构造成一个Node,扔进同步队列中,标记为独占锁模式,详细介绍在下面
final Node node = addWaiter(Node.EXCLUSIVE);
boolean failed = true;
try {
//进入自旋状态
for (;;) {
//如果前驱节点是头节点,将会再次try一下,如果成功了就讲自己设置为新的头节点
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return;
}
//这里会检测前驱节点的waitStatus是否已经设置为了Node.SIGNAL,并移出waitStatus>0即取消获取锁或中断了的线程节点;同时会尝试CAS设置前驱节点的waitStatus为Node.SIGNAL
//只有已经设置了当前线程才能安然的阻塞,否则没人叫醒他就一睡不醒了
if (shouldParkAfterFailedAcquire(p, node) &&
//这里会使用LockSupport阻塞线程同时返回线程的中断状态
//当线程被唤醒时会再次再这里执行代码,从而会对中断做出响应
parkAndCheckInterrupt())
throw new InterruptedException();
}
} finally {
if (failed)
//如果中断退出了会取消获取锁,其实就是对自己节点的清理,如清空Thread引用,标记waitStatus为cancle或移出自己
cancelAcquire(node);
}
}
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);
}
}
共享锁
看到三个方法都是先进行一次try,然后根据结果决定进入do方法,三个方法分别由自己对应的do方法
public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg);
}
public final boolean tryAcquireSharedNanos(int arg, long nanosTimeout)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
return tryAcquireShared(arg) >= 0 ||
doAcquireSharedNanos(arg, nanosTimeout);
}
以下三个方法与独占锁的清空差不多,中断和超时只是做了相应的判断,这里以中断为例注释讲解
private void doAcquireShared(int arg) {
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
if (p == head) {
int r = tryAcquireShared(arg);
if (r >= 0) {
setHeadAndPropagate(node, r);
p.next = null; // help GC
if (interrupted)
selfInterrupt();
failed = false;
return;
}
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
private void doAcquireSharedInterruptibly(int arg)
throws InterruptedException {
//加入到同步队列,标记为共享锁模式
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
for (;;) {
//头节点为前驱节点则可以进行锁的获取
final Node p = node.predecessor();
if (p == head) {
//尝试获取锁,共享锁的返回值>=0则代表下个线程获取锁成功的可能性很大
int r = tryAcquireShared(arg);
if (r >= 0) {
//这里在设置自己为头节点的同时还会唤醒后继节点,因为共享锁不需要等释放,只需要竞争线程获取锁后便可以进行锁的获取了
setHeadAndPropagate(node, r);
p.next = null; // help GC
failed = false;
return;
}
}
//这里与独占锁的情况相同
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
private boolean doAcquireSharedNanos(int arg, long nanosTimeout)
throws InterruptedException {
if (nanosTimeout <= 0L)
return false;
final long deadline = System.nanoTime() + nanosTimeout;
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
for (;;) {
final Node p = node.predecessor();
if (p == head) {
int r = tryAcquireShared(arg);
if (r >= 0) {
setHeadAndPropagate(node, r);
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);
}
}
private Node addWaiter(Node mode) {
//构建当前节点的Node,并标记模式,独占锁下mode为null,共享锁下mode为一个空Node对象,仅作为一个标识
Node node = new Node(Thread.currentThread(), mode);
// 这里会尝试直接设置为尾节点,如果存在竞争失败了或队列为空,则进入enq方法中自旋设置
Node pred = tail;
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);
return node;
}
//自旋设置尾节点,如果当前队列为空,则会先创建一个空节点作为头节点,这个空头节点是不代表线程的
private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) { // Must initialize
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
release
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
//waitStatus==0 代表已经唤醒了,其他状态下Signle、cancle、propagate都应该去唤醒后继节点
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
private void doReleaseShared() {
//注意这里存在循环
for (;;) {
Node h = head;
//这里保证队列中至少两个节点
if (h != null && h != tail) {
int ws = h.waitStatus;
if (ws == Node.SIGNAL) {
//CAS设置自己的WaitStatus,失败重试
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue; // loop to recheck cases
unparkSuccessor(h);
}
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue; // loop on failed CAS
}
//头节点发生改变会导致循环,
//头节点改变是在线程苏醒并获取到锁中进行的,因此在上面唤醒线程后,到这里发现头节点已经改变,说明唤醒的线程拿到了锁
//这时候可以在这里继续唤醒下面的节点去获取读锁,这是一种快速唤醒后继节点的优化手段
if (h == head) // loop if head changed
break;
}
}
//核心唤醒方法
private void unparkSuccessor(Node node) {
//注意这里的node是指头节点
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
//头节点的后继节点
Node s = node.next;
//waitStatus>0代表cancle了
//如果没有后继节点或者后继节点被取消了,则从队尾开始找正常节点,然后接上。
//是不是感觉有点矛盾?因为并发的原因,存在尾分叉的情况,表现为多个“尾节点”,而前向后遍历只能遍历到一个,并不能保证真的没有尾节点了,因此需要使用后向遍历来找
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);
}
总结
基于try*()和do*()的总结可知:
AQS中的锁的获取会先尝试一次性的获取,如果失败,根据情况进入同步队列中阻塞等待获取。对于独占锁,当一个线程获取到锁后,只有在释放时才会唤醒后继节点,由后继节点来进行锁的争取。而对于共享锁,当线程获取到锁以及线程释放锁的时候都会进行唤醒后继节点。
在这里又能看出另一个问题,无论独占还是共享,都是唤醒后继节点,由后继节点来进行锁的争取,这是不是意味着全都是公平锁?
详情看我博客AQS的非公平锁与同步队列的FIFO冲突吗?