AQS(acquireQueued(Node, int) 3)--队列同步器
1.acquireQueued(Node, int)
源码:
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);
// 说明前继节点已经释放掉资源了,将其next置空,以方便虚拟机回收掉该前继节点
p.next = null; // help GC
// 标识获取资源成功
failed = false;
// 返回中断标记
return interrupted;
}
// 若前继节点不是头结点,或者获取资源失败,
// 则需要通过shouldParkAfterFailedAcquire函数
// 判断是否需要阻塞该节点持有的线程
// 若shouldParkAfterFailedAcquire函数返回true,
// 则继续执行parkAndCheckInterrupt()函数,
// 将该线程阻塞并检查是否可以被中断,若返回true,则将interrupted标志置于true
if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())
interrupted = true;
}
} finally {
// 最终获取资源失败,则当前节点放弃获取资源
if (failed)
cancelAcquire(node);
}
}
具体看一下shouldParkAfterFailedAcquire函数:
// shouldParkAfterFailedAcquire是通过前继节点的waitStatus值来判断是否阻塞当前节点的线程的
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
// 获取前继节点的waitStatus值ws
int ws = pred.waitStatus;
// 如果ws的值为Node.SIGNAL(-1),则直接返回true
// 说明前继节点完成资源的释放或者中断后,会通知当前节点的,回家等通知就好了,不用自旋频繁地来打听消息
if (ws == Node.SIGNAL) return true;
// 如果前继节点的ws值大于0,即为1,说明前继节点处于放弃状态(Cancelled)
// 那就继续往前遍历,直到当前节点的前继节点的ws值为0或负数
// 此处代码很关键,节点往前移动就是通过这里来实现的,直到节点的前继节点满足
// if (p == head && tryAcquire(arg))条件,acquireQueued方法才能够跳出自旋过程
if (ws > 0) {
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
// 将前继节点的ws值设置为Node.SIGNAL,以保证下次自旋时,shouldParkAfterFailedAcquire直接返回true
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
parkAndCheckInterrupt()函数则简单很多,主要调用LockSupport类的park()方法阻塞当前线程,并返回线程是否被中断过。
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
至此,独占模式下,线程获取资源acquire的代码就跟完了,总结一下过程:
第一步:首先线程通过tryAcquire(arg)尝试获取共享资源,若获取成功则直接返回,若不成功,
则将该线程以独占模式添加到等待队列尾部,
tryAcquire(arg)由继承AQS的自定义同步器来具体实现;
第二步:当前线程加入等待队列后,会通过acquireQueued方法基于CAS自旋不断尝试获取资源,直至获取到资源;
第三步:若在自旋过程中,线程被中断过,acquireQueued方法会标记此次中断,并返回true。
第四步:若acquireQueued方法获取到资源后,返回true,则执行线程自我中断操作selfInterrupt()。
static void selfInterrupt() {
Thread.currentThread().interrupt();
}
2.释放资源(独占模式)
讲完获取资源,对应的讲一下AQS的释放资源过程,其入口函数为:
public final boolean release(int arg) {
if (tryRelease(arg)) {
// 获取到等待队列的头结点h
Node h = head;
// 若头结点不为空且其ws值非0,则唤醒h的后继节点
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
总结:逻辑并不复杂,通过tryRelease(arg)来释放资源,和tryAcquire类似,tryRelease也是有继承AQS的自定义同步器来具体实现。
函数:
tryRelease(int) --该方法尝试释放指定量的资源。
protected boolean tryRelease(int arg) {
throw new UnsupportedOperationException();
}
学习来源:https://www.jianshu.com/p/0f876ead2846