AbstractQueuedSynchronizer(aqs)中acquireShared和releaseShared的理解

上一篇我们看了aqs中独占模式下acquire和release的代码(https://blog.csdn.net/a6822342/article/details/84839391)。下面我们来看看共享模式下的acquireShared和releaseShared的代码。

首先先看acquireShared的代码

public final void acquireShared(int arg) {
    if (tryAcquireShared(arg) < 0)
        doAcquireShared(arg);
}

首先看能不能直接获得资源,也就是执行tryAcquireShared(arg)方法,如果没有取得资源,则返回是负数,如果取得了资源但是后续的资源可以再进行获取则返回的是0,如果返回的是正数,那么代表着获取成功并且还有剩余资源,别的线程也能进行获取。Ps:tryAcquireShared方法需要子类自己去实现。

如果返回值小于0,那么通过doAcquireShared(arg)进入等待队列,等待获取资源。

下面我们来看看doAcquireShared(arg)方法。

private void doAcquireShared(int arg) {
    final Node node = addWaiter(Node.SHARED);
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
            final Node p = node.predecessor();//找先驱结点
            if (p == head) {
                int r = tryAcquireShared(arg);//尝试获取资源
                if (r >= 0) {//获取到了资源
                    setHeadAndPropagate(node, r);//设置当前结点为头结点,然后去唤醒后续结点
                    p.next = null; // help GC  释放头结点,等待GC
                    if (interrupted)
                        selfInterrupt();
                    failed = false;//获取到资源
                    return;
                }
            }
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)//如果最后没有获取到资源,则cancel
            cancelAcquire(node);
    }
}

final Node node = addWaiter(Node.SHARED);首先还是把该线程新建一个结点,然后加入到队列中去,这一块和独占模式下的addWaiter代码差不多,不同的是结点的模式不一样。这里就不重复赘述了,可以去看https://blog.csdn.net/a6822342/article/details/84839391

定义一个标记failed,表示是否成功拿到资源的标记。

定义一个是否被中断的标记interrupted,默认是false。

然后就循环的去找当前结点的前驱结点,看它是不是头结点,如果是头结点的话,就让该前驱结点去获取资源,如果获取到资源的话,那么返回值r的值是大于等于0的,然后就会通过setHeadAndPropagate(node, r)方法把后续结点唤醒。

if (shouldParkAfterFailedAcquire(p, node) &&
        parkAndCheckInterrupt())
        interrupted = true;
}

这里是通过shouldParkAfterFailedAcquire和parkAndCheckInterrupt来判断自己是不是可以进入休息状态了,如果在休息状态的时候被中断过,则把interrupted设置为true,然后当自己成为头结点的时候返回。(和独占模式下的acquireQueued()方法内的意思一样)

private void setHeadAndPropagate(Node node, int propagate) {
    Node h = head; // Record old head for check below
    setHead(node);
    /*
     * Try to signal next queued node if:
     *   Propagation was indicated by caller,
     *     or was recorded (as h.waitStatus either before
     *     or after setHead) by a previous operation
     *     (note: this uses sign-check of waitStatus because
     *      PROPAGATE status may transition to SIGNAL.)
     * and
     *   The next node is waiting in shared mode,
     *     or we don't know, because it appears null
     *
     * The conservatism in both of these checks may cause
     * unnecessary wake-ups, but only when there are multiple
     * racing acquires/releases, so most need signals now or soon
     * anyway.
     */
    if (propagate > 0 || h == null || h.waitStatus < 0 ||
        (h = head) == null || h.waitStatus < 0) {
        Node s = node.next;
        if (s == null || s.isShared())
            doReleaseShared();//主要功能唤醒后续结点
    }
}

setHead(node)表示为把node设置为head结点,然后如果当前node获取完资源还有剩余(propagate>0)的话,就去唤醒后继的结点,也就是源码中这句英文的意思:Propagation was indicated by caller 。如果当前头结点的状态是<0,意味着它是SINGAL和Propagate,那么也去唤醒后继结点。如果头结点为空(可能在其它线程中被释放了),那么也唤醒后继结点来获取资源。

那么此时,s作为传进来的结点的后继结点,执行if (s == null || s.isShared())的判断,如果s为空(当前结点为空的话,当然要找后继结点)或者s是shared模式的话,也要唤醒后继结点,则执行doReleaseShared()方法去唤醒后继。

doReleaseShared()的代码放在将releaseShared()的代码里面分析。

 

下面我们来看看releaseShared()

public final boolean releaseShared(int arg) {
    if (tryReleaseShared(arg)) {
        doReleaseShared();
        return true;
    }
    return false;
}

首先去尝试释放资源tryReleaseShared(arg),如果释放成功了,就代表有资源空闲出来,那么就用doReleaseShared()去唤醒后续结点。

private void doReleaseShared() {
    /*
     * Ensure that a release propagates, even if there are other
     * in-progress acquires/releases.  This proceeds in the usual
     * way of trying to unparkSuccessor of head if it needs
     * signal. But if it does not, status is set to PROPAGATE to
     * ensure that upon release, propagation continues.
     * Additionally, we must loop in case a new node is added
     * while we are doing this. Also, unlike other uses of
     * unparkSuccessor, we need to know if CAS to reset status
     * fails, if so rechecking.
     */
    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;
    }
}

在自旋的阶段,每一次循环的过程都是首先获得头结点,如果头结点不为空且不为尾结点(阻塞队列里面只有一个结点),那么先获得该节点的状态,如果是SIGNAL的状态,则代表它需要有后继结点去唤醒,首先将其的状态变为0,因为是要释放资源了,它也不需要做什么了,所以转变为初始状态,然后去唤醒后继结点unparkSuccessor(h),如果结点状态一开始就是0,那么就给他转换成PROPAGATE状态,保证在后续获取资源的时候,还能够向后面传播(这一块不明白)。

 

如果在自旋的过程中,头结点改变了,那么就退出循环过程。

 

参考文章:

https://blog.csdn.net/liu88010988/article/details/50799819?utm_source=blogxgwz6

http://www.php.cn/java-article-372194.html

 

猜你喜欢

转载自blog.csdn.net/a6822342/article/details/84875304