队列同步器(AbstractQueuedSynchronizer)作为构建锁及其他同步器的基础框架,它通过volatile状态变量及FIFO队列实现线程获取或等待资源的逻辑,JUC中的同步组件(ReentrantLock、ReentrantReadWriteLock、CountDownLatch等)均基于此基础上实现。
AbstractQueuedSynchronizer的源码中,定义如下模板方法:
通过上述定义的模板办法,子类继承AbstractQueuedSynchronizer之后,根据特定需求,实现模板方法中调用的抽象方法,即可定制相应的同步框架,相应的抽象方法如下:
在AQS的模型中,将线程包装为Node(节点),节点的重要成员变量说明:
waitStatus值: =1节点设置为取消(超时或中断);
=-1后继节点处于等待
=-2节点位于等待队列
=-3下次获取共享式同步状态被传播
接下来,逐步分析AQS定义的同步方法
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
调用抽象tryAcquire(arg)尝试获取资源,获取失败则执行addWaiter(Node.EXCLUSIVE)将线程加入FIFO队列
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);//以独占模式封装线程的Node节点
Node pred = tail;//记录尾节点
if (pred != null) {//尾节点存在
node.prev = pred;
if (compareAndSetTail(pred, node)) {//CAS将node设置为尾节点
pred.next = node;
return node;
}
}
enq(node);//尾节点不存在或CAS node尾尾节点失败
return node;
}
private Node enq(final Node node) {
for (;;) {
Node t = tail;//获取当前为节点
if (t == null) { // 队列为空,构建头节点
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {// CAS将节点设置为尾节点
t.next = node;
return t;
}
}
}
}
将node添加至同步对列之后,执行acquireQueued(final Node node, int arg)线程进入自旋状态,,每个节点都在观察,当条件满足时,获取同步状态;否则,节点阻塞
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);
}
}
综上分析,独占式同步状态获取流程如下图:
独占式锁的释放,无多线程竞争,逻辑相当简单,只需调用抽象方法,并唤醒后继节
public final boolean release(int arg) {
if (tryRelease(arg)) {//调用抽象方法
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);//唤醒后继节点的阻塞线程
return true;
}
return false;
}
public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0)//获取失败,自旋获取同步状态
doAcquireShared(arg);
}
在共享式获取中,先尝试调用抽象方法获取锁,失败,则自旋获取
private void doAcquireShared(int arg) {
final Node node = addWaiter(Node.SHARED);//接入FIFO同步队列
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();//前驱节点
if (p == head) {//前驱节点为头节点
int r = tryAcquireShared(arg);
if (r >= 0) {//>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);
}
}
共享式获取流程与独占式获取大体一致,只是具体获取同步状态的规则不一致(由抽象方法实现),然而共享式释放同步资源则与独占释放不那么一样(主要在于共享式释放存在多线程竞争)
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {//抽象方法获取资源
doReleaseShared();//
return true;
}
return false;
}
private void doReleaseShared() {
for (;;) {
Node h = head;
if (h != null && h != tail) {//同步队列含有等待节点
int ws = h.waitStatus;
if (ws == Node.SIGNAL) {
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;
}
}
无论是共享式还是独占式获取,线程自旋观察过程中是否阻塞if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())由此实现
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
if (ws == Node.SIGNAL)//前驱节点状态=-1需要阻塞
return true;
if (ws > 0) {//节点因中断或超时被取消CACLLED
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);//跳过被取消的节点
pred.next = node;
} else {
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);//设置有效前节点状态
}
return false;
}
在shouldParkAfterFailedAcquire根据前节点状态来判断是否需要阻塞当前线程,然后执行parkAndCheckInterrupt()
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);//阻塞当前线程
return Thread.interrupted();//返回中断状态
}
共享式与独占式获取同步状态的流程以介绍未必,至于对应超时,及中断响应获取的状态的流程与上大体一致,只是在定义模板方法方法中添加了响应中断、超时等业务,具体并在一一分析。