【死磕 Java 并发】—– J.U.C 之 AQS:同步状态的获取与释放
摘要: 原创出处 http://cmsblogs.com/?p=2197 「小明哥」欢迎转载,保留摘要,谢谢!
在前面提到过,AQS 是构建 Java 同步组件的基础,我们期待它能够成为实现大部分同步需求的基础。
AQS 的设计模式采用的模板方法模式,子类通过继承的方式,实现它的抽象方法来管理同步状态。对于子类而言,它并没有太多的活要做,AQS 已经提供了大量的模板方法来实现同步,主要是分为三类:
-
独占式获取和释放同步状态
-
共享式获取和释放同步状态
-
查询同步队列中的等待线程情况。
自定义子类使用 AQS 提供的模板方法,就可以实现自己的同步语义。
1. 独占式
独占式,同一时刻,仅有一个线程持有同步状态。
1.1 独占式同步状态获取
#acquire(int arg)
方法,为 AQS 提供的模板方法。该方法为独占式获取同步状态,但是该方法对中断不敏感。也就是说,由于线程获取同步状态失败而加入到 CLH 同步队列中,后续对该线程进行中断操作时,线程不会从 CLH 同步队列中移除。代码如下
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
第 2 行:调用 #tryAcquire(int arg)
方法,去尝试获取同步状态,获取成功则设置锁状态并返回 true ,否则获取失败,返回 false 。若获取成功,#acquire(int arg)
方法,直接返回,不用线程阻塞,自旋直到获得同步状态成功。
#tryAcquire(int arg)
方法,需要自定义同步组件自己实现,该方法必须要保证线程安全的获取同步状态。代码如下:
protected boolean tryAcquire(int arg) {
throw new UnsupportedOperationException();
}
直接抛出 UnsupportedOperationException 异常。
第 3 行:如果 #tryAcquire(int arg)
方法返回 false ,即获取同步状态失败,则调用 #addWaiter(Node mode)
方法,将当前线程加入到 CLH 同步队列尾部。并且, mode
方法参数为 Node.EXCLUSIVE
,表示独占模式。
第 3 行:调用 boolean #acquireQueued(Node node, int arg)
方法,自旋直到获得同步状态成功。详细解析,见 「1.1.1 acquireQueued」 中。另外,该方法的返回值类型为 boolean
,当返回 true 时,表示在这个过程中,发生过线程中断。但是呢,这个方法又会清理线程中断的标识,所以在种情况下,需要调用【第 4 行】的 #selfInterrupt() 方法,恢复线程中断的标识,代码如下:
static void selfInterrupt() {
Thread.currentThread().interrupt();
}
1.1.1 acquireQueued
boolean #acquireQueued(Node node, int arg)
方法,为一个自旋的过程,也就是说,当前线程(Node)进入同步队列后,就会进入一个自旋的过程,每个节点都会自省地观察,当条件满足,获取到同步状态后,就可以从这个自旋过程中退出,否则会一直执行下去。
流程图如下:
代码如下:
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);
}
}
1.1.2 shouldParkAfterFailedAcquire
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;
}
- 第 4 至 9 行:等待状态为
Node.SIGNAL
时,表示pred
的下一个节点node
的线程需要阻塞等待。在pred
的线程释放同步状态时,会对node
的线程进行唤醒通知。所以,【第 9 行】返回 true ,表明当前线程可以被 park,安全的阻塞等待。