【Java併發編程】AQS(3)——獨佔鎖的釋放

今天主要講AQS中對獨佔鎖的釋放,如果大家把昨天“獨佔鎖的獲取”看完了,今天這篇文章將會很輕鬆!

 

AQS在獨佔模式下,對鎖的釋放只有release方法,而release方法其實就做了兩件事:釋放鎖和喚醒後繼Node(準確講是Node中的線程,後面爲了方便統一稱爲Node)。下面我們直接看源碼吧


public final boolean release(int arg) {
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }
    return false;
}

我們可以看到,裏面其實就只涉及到兩個方法,tryRelease和unparkSuccessor,分別對應釋放鎖和喚醒後繼Node,我們先梳理下release的整個邏輯再具體來看這兩個方法

我們首先會通過tryRelease來釋放鎖,如果釋放失敗,則會返回false,否則就會進入到if分支中,在if分支中,我們必須滿足(h != null && h.waitStatus != 0) 纔會去喚醒後繼Node。換句話說,我們只要滿足下麪條件中的一個,就不會去喚醒後繼Node

  1. h==null

  2. h != null && h.waitStatus == 0

第一點h == null 這個條件很好理解,如果頭結點爲空了,那麼同步隊列肯定也是空的,所以此時是沒有Node需要去喚醒的

第二點頭結點不爲空但狀態爲0是什麼情況呢?“併發三板斧”中我們說過,我們在Node初始化時,會把Node的狀態置爲0,我們再回想下昨天“獨佔鎖的獲取”中的enq方法,我們是在這裏將同步隊列初始化的(即生成head與tail),此時同步隊列中也是沒有等待喚醒的Node

最後需要注意的是,release方法的返回值只與釋放鎖有關,和喚醒線程是沒有關係的。好的,邏輯理清楚了,接下來就具體看下兩個方法吧

 

1 tryRelease

這個方法是留給子類實現的,用來釋放鎖,如果釋放成功則返回true,失敗返回false,這個具體的實現我們放在子類中講解,這裏就不拓展了

 

2 unparkSuccessor

此方法是喚醒後繼Node,我們看具體代碼:

private void unparkSuccessor(Node node) {

    int ws = node.waitStatus;
    if (ws < 0)
        compareAndSetWaitStatus(node, ws, 0);

    Node s = node.next;
    if (s == null || s.waitStatus > 0) {
        s = null;
        for (Node t = tail; t != null && t != node; t = t.prev)
            if (t.waitStatus <= 0)
                s = t;
    }
    if (s != null)
        LockSupport.unpark(s.thread);
}

如果結點的waitStatus<0,說明此時waitStatus爲SIGNAL(僅在獨佔模式下),我們此時需要將waitStatus置爲0,因爲待會會要喚醒後繼節點

接着,我們拿到這個Node的後繼Node,如果這個後繼Node爲空或者waitStatus屬性大於0,說明這個Node已經不存在或者已被取消,則我們需要從尾結點開始往前遍歷找到那個離head最近的需要被喚醒的Node,即進入第二個if分支

爲啥是要從後往前遍歷呢,這和我們“獨佔鎖的獲取”中講的入隊操作的順序有關。獨佔模式下調用addWaiter方法入隊時,是先將入隊Node的前驅Node指向此時的尾Node,然後再通過CAS將tail指向入隊Node,如果成功了(這步成功就標誌着此Node已經入隊成功,其他競爭的Node需要重新入隊),纔會將原來的尾Node的後繼Node指向入隊Node,代碼如下

node.prev = pred;   //step1
if (compareAndSetTail(pred, node))   // step2
     pred.next = node;  // step3

假設我們此時有個Node正在入隊,執行完step2,還未執行step3,如果unparkSuccessor採用從head往後遍歷的話,此時是找不到這個新插入的Node的;但如果是採用從後往前遍歷,則不會出現這個問題。如果對這個不太理解,可以去我上一篇文章“獨佔鎖的獲取”看看addWaiter方法的講解,裏面畫了三幅圖可以幫助理解併發時的入隊情況,從而明白這裏爲什麼需要從後往前遍歷

最後遍歷完後,我們會進入第三個if判斷,如果s不爲空,說明s就是隊列中需要被喚醒的且最靠近head的節點,我們使用unpark方法將其喚醒就可以了

好了,獨佔鎖的釋放就講完了,是不是比起獨佔鎖的獲取簡單多了,當然,覺得簡單的前提是你已經理解了獨佔鎖的獲取了

 

 

(未完)

歡迎大家關注我的公衆號 “程序員進階之路”,裏面記錄了一個非科班程序員的成長之路

                                                         

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