前言
上文主要介绍了acquire的方法,本文将继续探讨剩下的一些方法。
正文
关于acquireInterruptibly
这个方法和acquire
代码差异不大,主要在896行多了个中断异常处理,acquire
是直接返回中断的状态值
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
throw new InterruptedException();
}
关于tryAcquireNanos
方法也是大同小异,差异在doAcquireNanos
内关于获取锁失败后判断是否阻塞的时候加个超时的判断条件,代码在932行
if (shouldParkAfterFailedAcquire(p, node) &&
// 加个自旋的超时判断
nanosTimeout > spinForTimeoutThreshold)
LockSupport.parkNanos(this, nanosTimeout);
看下release
释放锁方法
public final boolean release(int arg) {
// 由子类覆写
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
// 唤醒下一个节点,通过调用LockSupport来实现
unparkSuccessor(h);
return true;
}
return false;
}
接下来看下获取共享锁的方法acquireShared
public final void acquireShared(int arg) {
// 子类覆写
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
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);
// r>=0 说明共享锁获取成功
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);
}
}
如果读者看过acquire
方法或者看过笔者上篇关于AQS的介绍文章的话,发现两种方法差异不大,主要在setHeadAndPropagate,独占锁是没有这方法的
/**
* Sets head of queue, and checks if successor may be waiting
* in shared mode, if so propagating if either propagate > 0 or
* PROPAGATE status was set.
*
* @param node the node
* @param propagate the return value from a tryAcquireShared
*/
// propagat值为之前tryAcquireShared返回的值,如果大于0 ,表示会继续扩散
// 后续节点扔可以获得锁,会继续唤醒后续节点
private void setHeadAndPropagate(Node node, int propagate) {
Node h = head; // Record old head for check below
setHead(node);
/*
* Try to signal next queued node if:
* Propagation was indicated by caller,
* or was recorded (as h.waitStatus either before
* or after setHead) by a previous operation
* (note: this uses sign-check of waitStatus because
* PROPAGATE status may transition to SIGNAL.)
* and
* The next node is waiting in shared mode,
* or we don't know, because it appears null
*
* The conservatism in both of these checks may cause
* unnecessary wake-ups, but only when there are multiple
* racing acquires/releases, so most need signals now or soon
* anyway.
*/
// propagate大于0或者节点状态为负包括以及头结点的检查
if (propagate > 0 || h == null || h.waitStatus < 0 ||
(h = head) == null || h.waitStatus < 0) {
Node s = node.next;
// 后续节点为null或者是共享,继续唤醒节点
if (s == null || s.isShared())
doReleaseShared();
}
}
/**
* Release action for shared mode -- signals successor and ensures
* propagation. (Note: For exclusive mode, release just amounts
* to calling unparkSuccessor of head if it needs signal.)
*/
private void doReleaseShared() {
/*
* Ensure that a release propagates, even if there are other
* in-progress acquires/releases. This proceeds in the usual
* way of trying to unparkSuccessor of head if it needs
* signal. But if it does not, status is set to PROPAGATE to
* ensure that upon release, propagation continues.
* Additionally, we must loop in case a new node is added
* while we are doing this. Also, unlike other uses of
* unparkSuccessor, we need to know if CAS to reset status
* fails, if so rechecking.
*/
for (;;) {
// 头节点持有锁
Node h = head;
if (h != null && h != tail) {
int ws = h.waitStatus;
// SIGNAL状态
if (ws == Node.SIGNAL) {
// cas赋值0成功后,就唤醒节点
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue; // loop to recheck cases
// 唤醒节点
unparkSuccessor(h);
}
// ws=0,尝试cas赋值PROPAGATE
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue; // loop on failed CAS
}
// 终止自旋,因为共享锁说明也有其他线程正在使用,会修改head的值
// 即自旋到确定没有其他线程修改的情况下。
if (h == head) // loop if head changed
break;
}
}
关于doReleaseShared
这个方法,看似代码不多,其实思想还是很复杂的。
首先doReleaseShared
这个方法在acquireShared
中调用了一次,释放锁的方法releaseShared
也会调用doReleaseShared
,这就耐人寻味了。
从setHeadAndPropagate
如果头结点下的结点为null或者是共享结点,就唤醒下个节点,因为是共享锁,所以支持多线程持有锁。
而在doReleaseShared
中了解只有头节点才获取锁,代码里只判断头节点的等待状态,通过cas更改status值为0来唤醒节点。
因为是共享锁,支持多线程喽,所以就会有多个线程调用acquireShared
和releaseShared
方法了。在看一下终止自旋条件if (h == head)
说明只有头节点不被易主的时候才能结束。
说的难以理解,举例解释一下。
比如现有的同步队列,有abcd四个节点
head->a->b->c->d
a成为head后
head(a)->b->c->d
a将会调用doReleaseShared唤醒b,方便描述用doReleaseShared(a)来表达a线程的调用。唤醒后b将成为新的head
head(b)->c->d
这样下去b也会调用doReleaseShared方法去唤醒c,然后不停的循环下去,不断增加唤醒的线程操作,大大的提升了队列唤醒线程的效率,而代码中的第一个if循环以及cas操作确保诸多线程中只有一个能唤醒成功。不知有人是否好奇此时唤醒b线程的线程a在干嘛,线程a在唤醒b后继续往下走
,走到if (h == head)
发现头结点变更了,就进行自旋。
如果doReleaseShared(a) 走完后,b还没有变成新的头结点时候,自旋不就终止了,a不就退出了,即使这样也没有问题,b已经被唤醒了,会继续调用doReleaseShared方法来唤醒下个节点,只是少了a线程的唤醒,效率可能会低一些,并不影响功能。
所以
if (ws == Node.SIGNAL) {
// cas赋值0成功后,就唤醒节点
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 }
什么样的情况下才会cas失败,那就是ws值不为0喽,不为0那不是短路了吗,第一个ws== 0条件就过不去,所以只有可能的情况是ws==0成功了,走到cas判断值,有其他线程更改了ws的值。那什么情况下ws的值才会被更改呢,首先得考虑什么情况下ws的值才为0。
- 第一种,走进第一个if里面,cas将waitstatus值更改为0,那这样就唤醒节点了,不会走到else if下。
- 第二种,就是队尾节点。上文中曾提到waitstatus的枚举值,也知道队列的特性队尾进,队头出。所以新入队的节点都会重新成为队尾,我们知道节点的默认状态为0,而且
shouldParkAfterFailedAcquire 此方法中提到过争取锁失败的时候
加入队列中会修改前驱节点的waitstatus值为SIGNAL,所以可以大胆的猜测,当前节点为尾节点,所以waitstatus值等于0,走到cas判断的时候,有新的节点入队,此时新节点调用shouldParkAfterFailedAcquire将前驱节点的waitstatus值更改为 SIGNAL,才会出现这种情况。
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
if (ws == Node.SIGNAL)
/*
* This node has already set status asking a release
* to signal it, so it can safely park.
*/
return true;
if (ws > 0) {
/*
* Predecessor was cancelled. Skip over predecessors and
* indicate retry.
*/
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
/*
* waitStatus must be 0 or PROPAGATE. Indicate that we
* need a signal, but don't park yet. Caller will need to
* retry to make sure it cannot acquire before parking.
*/
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
达到这种状况条件是很苛刻的:
- 第一,
if (h != null && h != tail) {
这个队列至少有两个节点 - 第二,h的waitstatus==0判断条件满足即是尾节点
- 第三,走进cas的判断值有节点入队。
总结
关于AQS的介绍大概就到此结束了,本系列详细介绍了AQS的思想和原理,以及独占锁和共享锁的区别,分别介绍了两种在获取锁以及释放锁的差异。