AQS源碼深度解析之cancelAcquire方法解讀

1.背景

2.源碼解讀

調用該方法的地方

 方法源碼解讀

    /**
     * 取消獲取資源(異常處理時都需要用到)
     * 方法主要功能:
     * 1.處理當前取消節點的狀態;
     * 2.將當前取消節點的前置非取消節點和後置非取消節點"鏈接"起來;
     * 3.如果前置節點釋放了鎖,那麼當前取消節點承擔起後續節點的喚醒職責。
     *
     * @param node
     */
    private void cancelAcquire(Node node) {
        // 如果節點爲空直接返回
        if (node == null)
            return;
        // 處理當前取消節點的狀態;
        node.thread = null;
        node.waitStatus = Node.CANCELLED;
        /*
         *這段代碼用來找到前置的非取消節點
         */
        Node pred = node.prev;
        while (pred.waitStatus > 0) { // pred.waitStatus > 0 說明當前節點的前置節點已取消,應該繼續向前找
            pred = pred.prev;
            node.prev = pred;
        }
        /*
         *這裏注意一下:
         *  1.pred是一個實際有效的前節點,即前置的非取消節點
         *  2.pred.next 並不一定是 node節點,因爲 while (pred.waitStatus > 0)前節點會向前移動
         */
        Node predNext = pred.next;
        // 修改尾指針:compareAndSetTail(node, pred),指向前置的第一個非取消節點;
        if (node == tail && compareAndSetTail(node, pred)) {
            // 將新的尾節點的next指針置空:compareAndSetNext(pred, predNext, null);
            // 爲什麼呢? 因爲node==tail,說明node節點的前一個非取消節點就是就會說最後一個節點,即無下一個節點
            compareAndSetNext(pred, predNext, null);
        } else {
            int ws;
            // compareAndSetWaitStatus(pred, ws, Node.SIGNAL) 把前置節點設置爲下一個獲取鎖的節點
            /*
             * 這個if的作用是:讓pred節點作爲下一個可以獲取鎖的節點
             * 1.pred != head && pred.thread != null
             * 2. pred.waitStatus=-1 獲取 compareAndSetWaitStatus(pred, ws, Node.SIGNAL
             */
            if (pred != head &&
                    ((ws = pred.waitStatus) == Node.SIGNAL ||
                            (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
                    pred.thread != null) {
                Node next = node.next;
                if (next != null && next.waitStatus <= 0)
                    // 設置前置節點的下一個節點爲:當前節點的下一個節點
                    compareAndSetNext(pred, predNext, next);
            } else {
                // 到這裏說明 pred是頭結點,喚醒node的下一個節點
                unparkSuccessor(node);
            }
            node.next = node; // help GC
        }
    }

重點邏輯圖解說明

 while (pred.waitStatus > 0)
            node.prev = pred = pred.prev;

假設當前隊列如下圖:

當前節點爲 t4前一個節點t3的waitStatus=1>0,會繼續向前找,最後會找到t2爲非取消的前置節點,循環結束

 如果當節點是尾節點

        /*
         *這裏注意一下:
         *  1.pred是一個實際有效的前節點,即前置的非取消節點
         *  2.pred.next 並不一定是 node節點,因爲 while (pred.waitStatus > 0)前節點會向前移動
         */
        Node predNext = pred.next;
        // 修改尾指針:compareAndSetTail(node, pred),指向前置的第一個非取消節點;
        if (node == tail && compareAndSetTail(node, pred)) {
            // 將新的尾節點的next指針置空:compareAndSetNext(pred, predNext, null);
            // 爲什麼呢? 因爲node==tail,說明node節點的前一個非取消節點就是就會說最後一個節點,即無下一個節點
            compareAndSetNext(pred, predNext, null);
            // 方法執行結束
        }

代碼執行隊列圖:


當前節點不是尾節點的情況

 int ws;
            // compareAndSetWaitStatus(pred, ws, Node.SIGNAL) 把前置節點設置爲下一個獲取鎖的節點
            /*
             * 這個if的作用是:讓pred節點作爲下一個可以獲取鎖的節點
             * 1.pred != head && pred.thread != null
             * 2. pred.waitStatus=-1 獲取 compareAndSetWaitStatus(pred, ws, Node.SIGNAL
             */
            if (pred != head &&
                    ((ws = pred.waitStatus) == Node.SIGNAL ||
                            (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
                    pred.thread != null) {
                Node next = node.next;
                if (next != null && next.waitStatus <= 0)
                    // 設置前置節點的下一個節點爲:當前節點的下一個節點
                    compareAndSetNext(pred, predNext, next);
            } else {
                // 到這裏說明 pred是頭結點,喚醒node的下一個節點
                unparkSuccessor(node);
            }
            node.next = node; // help GC

 

執行示意圖:

 

 如果待取消的是t1節點,且t2節點是取消節點,則會直接執行unparkSuccessor(node);,這個過程會從tail指針開始從後往前,找到最靠近頭的有效(非取消)節點,喚醒這個線程。

完美!

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