获取资源
AQS获取资源是通过各种acquire方法。不同acquire方法之间存在区别,如下:
- acquire:以互斥模式获取资源,忽略中断
- acquireInterruptibly:以互斥模式获取资源,响应中断
- acquireShared:以共享模式获取资源,忽略中断
- acquireSharedInterruptibly:以共享模式获取资源,响应中断
获取互斥资源
忽略中断的acquire方法
acquire方法是获取互斥资源,忽略中断。如果获取成功,直接返回,否则该线程会进入同步队列阻塞等待。源码如下:
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
acquire是一个模板方法,定义为final方法防止子类重写。其中的钩子方法tryAcquire需要子类去实现。
如果tryAcquire返回true,说明尝试获取成功,直接返回即可。如果tryAcquire返回false,说明尝试获取失败,会调用addWaiter方法进入等待队列。该方法的解析见上一篇博客《全网最详细的AbstractQueuedSynchronizer(AQS)源码剖析(一)AQS基础》。当该线程处于同步队列中(queued),就会调用acquireQueued方法
acquireQueued方法为一个已经位于同步队列的线程,以互斥模式获取资源,不响应中断但是会记录中断状态。源码如下:
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor(); // 获取node的前一个节点
if (p == head && tryAcquire(arg)) { // 如果p是head,说明node是队列头,可以竞争资源
setHead(node); // 将node出队
p.next = null; // help GC
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
在acquireQueued方法代码主要都包含在一个for循环中。如果发现node是队首节点,就会再次尝试获取资源。如果此时获取成功,就直接出队并返回,不用阻塞等待,这里体现了同步队列先进先出的特点
如果不是队首节点,或者是再次尝试获取资源又双叒叕失败了,则调用shouldParkAfterFailedAcquire方法判断当前线程是否应该被阻塞(在这里打一个断点)
shouldParkAfterFailedAcquire方法会检查当前线程是否应该被阻塞,如果是就返回true,否则返回false。其源码如下:
// 调用此方法必须保证pred是node的直接前驱,即node.prev == pred
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) {
// 如果前面的Node都被cancel了,那么就跳过这些Node
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;
}
只有当node的直接前驱节点状态是SIGNAL时,才会认为该线程应该被阻塞。否则还需要回到acquireQueued的for循环中重新检查,不会立即阻塞
我画了一张shouldParkAfterFailedAcquire的执行流程图,如下: