AbstractQueuedSynchronizer(aqs)中acquireShared和releaseShared的理解

上一篇我們看了aqs中獨佔模式下acquire和release的代碼。下面我們來看看共享模式下的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

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章