Java併發AQS原理分析(二)

上一篇詳細的分析了獨佔模式下如何對線程進行處理:簡單的總結是Java面向用戶提供了鎖的機制,後面的實現使用了一個同步隊列,因爲隊列具備先進先出的特色,把每一個線程都構形成爲隊列中的節點,每一個節點定義一個狀態值,符合狀態的節點(線程)才能夠有執行的機會,執行完釋放,後面的線程只能是等待着前面的執行結果進行判斷,每一個線程的執行都是獨立的,不能有其餘的線程干擾,因此在用戶的角度來看線程是在同步的執行的,而且是獨佔式的。html

共享式和獨佔式的區別主要是可否在同一時刻不一樣的線程獲取到同步狀態
node

上圖能夠很直觀的看出獨佔式和共享式的區別。在對一個資源進行訪問的時候,對讀操做是共享式的,而對寫操做是獨佔式的。併發

下面接着分析共享模式下線程之間是怎麼實現的?
依然找到方法執行的入口,在上一篇咱們找到了這幾種方式的頂層方法。ui

共享模式同步狀態的獲取:

acquireShared()方法中,調用了tryAcquireShared()返回一個狀態值,進行判斷,獲取成功直接返回,失敗進入等待隊列中。this

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

方法執行的順序:線程

  1. 調用tryAcquireShared()方法嘗試去獲取資源,具體實現是在子類中進行實現,成功直接返回,失敗執行下面的方法。
  2. 調用doAcquireShared()方法,線程進入等待隊列,等待獲取資源。

tryAcquireShared()方法是在子類中實現的,這裏不須要討論,可是返回值已是定義好的。方法返回一個int類型的值,當返回值大於0的時候,表示可以獲取到同步狀態。code

doAcquireShared()方法:
在前面分析過的方法這裏再也不分析htm

private void doAcquireShared(int arg) {
        //調用addWaiter方法把線程加入隊列尾
        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
                        if (interrupted)
                            //若是被中斷過,從新設置中斷
                            selfInterrupt();
                        failed = false;
                        return;
                    }
                }
                //判斷線程狀態移除或者掛起
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

基本上和獨佔式的處理方式一致,根本的體現就是在下面的這個setHeadAndPropagate()方法
setHeadAndPropagate():對象

private void setHeadAndPropagate(Node node, int propagate) {
        Node h = head; 
        setHead(node);
        //若是還有資源剩餘,繼續喚醒後面挨着的線程
        if (propagate > 0 || h == null || h.waitStatus < 0) {
            Node s = node.next;
            if (s == null || s.isShared())
                doReleaseShared();
        }
    }

在獨佔模式下獲取資源,即便資源還有剩餘,隊列中的後一個線程節點也不會被喚醒執行,只有本身佔有纔是獨佔,而共享式的是有剩餘就會給後面。blog

parkAndCheckInterrupt():

private final boolean parkAndCheckInterrupt() {
        //讓當前對象中止執行,阻塞狀態
        LockSupport.park(this);
        //檢查中斷
        return Thread.interrupted();
    }

cancelAcquire():

private void cancelAcquire(Node node) {
        if (node == null)
            return;
        node.thread = null;
        //得到前驅結點
        Node pred = node.prev;
        //若是狀態值大於0
        while (pred.waitStatus > 0)
            //移除當前節點的前驅結點
            node.prev = pred = pred.prev;
        //設置節點狀態值爲CANCELLED
        node.waitStatus = Node.CANCELLED;
        //若是是尾節點,設置尾節點
        if (node == tail && compareAndSetTail(node, pred)) {
            compareAndSetNext(pred, predNext, null);
        } else {
            int ws;
            //當節點既不是尾節點,也不是頭節點的後繼節點時,下面的這些判斷其實執行的出隊操做起做用的就是compareAndSetNext()方法將pred指向後繼節點
            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 {
                //若是node是head的後繼節點,則直接喚醒node的後繼節點
                unparkSuccessor(node);
            }
            node.next = node; // help GC
        }
    }

共享模式同步狀態的釋放:

嘗試釋放資源若是成功,會喚醒後續處於等待狀態中的節點

public final boolean releaseShared(int arg) {
        //嘗試釋放資源
        if (tryReleaseShared(arg)) {
            //喚醒後繼節點
            doReleaseShared();
            return true;
        }
        return false;
    }

doReleaseShared():

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;            
                    unparkSuccessor(h);
                }
                else if (ws == 0 &&
                         !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                    continue;                
            }
            if (h == head)                   
                break;
        }
    }

和獨佔式的釋放從代碼上看他們的區別是共享式的釋放方法中有一個循環。

引用:http://www.javashuo.com/article/p-xcevmtwv-gz.html

上面方法的流程也比較簡單,一句話:釋放掉資源後,喚醒後繼。跟獨佔模式下的release()類似,但有一點稍微須要注意:獨佔模式下的tryRelease()在徹底釋放掉資源(state=0)後,纔會返回true去喚醒其餘線程,這主要是基於獨佔下可重入的考量;而共享模式下的releaseShared()則沒有這種要求,共享模式實質就是控制必定量的線程併發執行,那麼擁有資源的線程在釋放掉部分資源時就能夠喚醒後繼等待結點。例如,資源總量是13,A(5)和B(7)分別獲取到資源併發運行,C(4)來時只剩1個資源就須要等待。A在運行過程當中釋放掉2個資源量,而後tryReleaseShared(2)返回true喚醒C,C一看只有3個仍不夠繼續等待;隨後B又釋放2個,tryReleaseShared(2)返回true喚醒C,C一看有5個夠本身用了,而後C就能夠跟A和B一塊兒運行。而ReentrantReadWriteLock讀鎖的tryReleaseShared()只有在徹底釋放掉資源(state=0)才返回true,因此自定義同步器能夠根據須要決定tryReleaseShared()的返回值。


在共享模式下,資源有剩餘就會給後面的鄰居,不會本身佔有,這是和獨佔式的根本區別。

相關文章
相關標籤/搜索