获取资源

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的执行流程图,如下:

全网最详细的AbstractQueuedSynchronizer(AQS)源码剖析(二)资源的获取和释放-LMLPHP
12-12 14:13