1. 程式人生 > >併發系列(九)-----AQS詳解共享模式的資源獲取與釋放

併發系列(九)-----AQS詳解共享模式的資源獲取與釋放

一 簡介

    到目前為止已經知道了AQS中同步佇列的基本的工作原理可以總結為維護同步佇列,獲取資源和改變執行緒狀態。上一篇文章中主要總結了獨佔模式下的資源獲取。這篇主要總結一下AQS中的共享模式。

   共享模式從字面上理解就是,這個資源可以被多個執行緒共享。在獨佔模式下,我們知道state的狀態最初的值是0.如果某個執行緒獲取到資源了state就加了1釋放資源了就減去1。當state變為0的時候喚醒後繼節點的執行緒,讓後繼節點的執行緒去持有資源。那麼好了我們可以不可以這麼幹,一開始我給state設定一個值,當一個執行緒獲取資源後,我的state就減去1,其它在來時我在減去1...以此類推,直到執行緒獲取資源減到為0為止。表示資源沒有了其他執行緒就無法獲取了只能去等待了。這樣的話多個執行緒就將這個state共享了,其實這就是AQS中的共享模式。

二 共享資源的獲取

   上面我們已經總出了它的大體邏輯了,可能在實現中還有一些細節需要去理解。下面看共享資源的獲取

    /**
     *共享資源的獲取
     *
     * @param arg 要獲取的資源數
     */
    public final void acquireShared(int arg) {
        //獲取共享鎖小於0表示資源沒有了,也就獲取失敗了
        if (tryAcquireShared(arg) < 0) {
            //真正的去獲取資源的方法
            doAcquireShared(arg);
        }
    }

 上面的方法中有關tryAcquireShared()我們就不必看了,看的話也是拋了一個異常,但是可以去看看它子類的實現比如Semaphore的實現,如果小於0表示沒有獲取到資源。看一下沒有獲取到資源會怎麼做,下面是原始碼。

 /**
     * 共享模式的資源競爭
     *
     * @param 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) {
                    //如果前驅節點時頭接節點的話,再次獲取資源r代表的是剩餘資源
                    int r = tryAcquireShared(arg);
                    if (r >= 0) {
                        //大於0獲取到了就要檢查還有沒有資源,有的話還要去繼續去喚醒下一個節點
                        setHeadAndPropagate(node, r);
                        p.next = null;
                        if (interrupted) {
                            selfInterrupt();
                        }
                        failed = false;
                        return;
                    }
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                        parkAndCheckInterrupt()) {
                    interrupted = true;
                }
            }
        } finally {
            if (failed) {
                cancelAcquire(node);
            }
        }
    }

上面的方法和獨佔模式下的整體上沒有太大的區別,但是在共享模式下獲取到資源後加了一個setHeadAndPropagate()這個方法,這個方法是用來判斷資源還有剩餘且下一個節點是否是共享節點的。下面是原始碼

    /**
     * 設定佇列頭部,並檢查後繼者是否在等待 在共享模式下,
     *
     * @param node      node節點
     * @param propagate 剩餘資源數
     */
    private void setHeadAndPropagate(Node node, int propagate) {
        //獲取頭節點
        Node h = head;
        //將剛獲取到資源的節點設定為頭節點
        setHead(node);
        //propagate>0表示還有資源可以獲取
        if (propagate > 0 || h == null || h.waitStatus < 0 ||
                (h = head) == null || h.waitStatus < 0) {
            Node s = node.next;
            //如果當前節點的下一個節點為空,或者下一個節點是共享
            //節點的時
            if (s == null || s.isShared()) {
                //釋放資源
                doReleaseShared();
            }
        }
    }

上面這個方法用來將剛獲取到資源的節點設定為頭節點,同時還檢查了有沒有資源和有沒有共享節點。如果有的話釋放資源(注意的釋放資源並沒有操作state只是做了喚醒下一個節點,由下一個節點的執行緒去操作資源)下面是原始碼

/**
     *
     *
     * 喚醒下一個節點
     */
    private void doReleaseShared() {
        for (; ; ) {
            //獲取頭
            Node h = head;
            //如果頭節點不為空,又不是尾節點的話
            if (h != null && h != tail) {
                //獲取當前頭節點的狀態
                int ws = h.waitStatus;
                if (ws == Node.SIGNAL) {
                    //更新成功了的話喚醒下一個節點
                    if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0)) {
                        continue;
                    } else {
                        //喚醒下一個節點
                        unparkSuccessor(h);
                    }
                    //將節點狀態設定為-3傳播狀態
                } else if (ws == 0 && !compareAndSetWaitStatus(h, 0, Node.PROPAGATE)) {
                    continue;
                }
            }
            //這裡當喚醒執行緒的時候這裡的頭節點head是可能變得,一但獲取成功就變了,而h還是以前頭節點的引用
            if (h == head) {
                break;
            }
        }
    }

這個方法可能有這樣一個問題,這個迴圈不就迴圈一次嗎?為什麼要用死迴圈呢?其實不是的,h是一個頭節點的引用,這個h可能是一箇舊的頭節點,因為在上一個方法中有設定頭節點的方法,head是獲取資源的執行緒節點,在成功的喚醒了下一個節點而且被喚醒的節點前驅節點又是頭節點,資源有存在且沒有中斷的話是可以獲取成功的獲取資源的,那麼這個head就有可能是被修改了,所以for迴圈才會一直繼續.。才會繼續去拿資源一直拿到沒有為止。

三 共享資源的釋放

在上面的方法中已經介紹了資源的釋放,而且資源的釋放就是操作state和喚醒下一個節點,下面是原始碼的

    /**
     * 釋放資源
     * 
     * @param arg 要釋放的資源數
     * @return 是否釋放成功
     */
    public final boolean releaseShared(int arg) {
        if (tryReleaseShared(arg)) {
            doReleaseShared();
            return true;
        }
        return false;
    }

呼叫tryReleaseShared(int)由子類實現去操作並state,返回boolean內部呼叫doReleaseShared()方法。doReleaseShared()方法在上文已經介紹過了。

四 總結

   共享模式的資源獲取與釋放主要在於將資源預先初始化一定數目,然後各個執行緒去獲取特定數量的資源,在獲取資源時如果資源還有剩下就喚醒後繼節點繼續獲取,知道資源沒有剩餘,其他資源只能進入等待狀態。釋放時做了操作資源和喚醒下一個節點的操作。