天天看點

JUC源碼分析7-locks-AQS-共享模式

AQS中一定要記住2點:

1.處理流程:

if(!請求成功)

加入隊列

2.請求是對state的判斷,AQS不關心你state表示什麼,你可以表示狀态也可以表示數量,由子類實作對請求的判斷。将規則的判斷和規則的處理分離,有點像模闆模式。

先想想什麼是獨占什麼是共享,舉個栗子:獨占就像大家拿号去排隊體檢,你拿号了發現前面還有n個人,沒辦法,等吧,然後你前面的人體檢完了,醫生就說,你通知下一位吧,ok,你出來通知排你後面的人,這個人有可能是跟占座位似得就放在紙在哪,是以你跳過他,再通知後面真正有人的進去。而共享則不同了,這個号可能不止屬于你一個人,可能屬于你公司所有體檢的人,是以拿号排隊輪到你的時候,你就需要通知排隊的所有同僚,大家一起體檢去啊(這個共享的不太恰當,應該當成condition例子來說才好)。感覺獨占和共享的大概意思就是這樣。

還是看下AQS的共享模式

public final void acquireShared(int arg) {
//tryAcquireShared請求判斷又是由子類實作判斷
    if (tryAcquireShared(arg) < 0)
    //失敗後加入隊列
        doAcquireShared(arg);
}
           
private void doAcquireShared(int arg) {
//節點狀态是共享,之前的獨占模式是EXCLUSIVE
//跟獨占一樣加入隊列
    final Node node = addWaiter(Node.SHARED);
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
        //還是判斷pre是不是頭結點,是就再次請求
            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);
    }
}
           

發現共享整個挂起的doAcquireShared方法跟獨占模式的acquireQueued處理差不多,唯一有差別的似乎就是如果pre節點是頭結點,目前節點請求成功,獨占模式隻是将節點設定為head然後return,這裡除了sethead還将喚醒隊列的中其他節點,其他方法不管,繼續看setHeadAndPropagate:

private void setHeadAndPropagate(Node node, int propagate) {
    Node h = head; // Record old head for check below
    setHead(node);

    if (propagate > 0 || h == null || h.waitStatus < 0) {
        Node s = node.next;
        //目前node請求成功後判斷next節點是共享節點就繼續release
        if (s == null || s.isShared())
            doReleaseShared();
    }
}
private void doReleaseShared() {
    /*
			使用for保證隊列中節點一定會被傳遞,即使有其他acquire或release在進行
     */
    for (;;) {
        Node h = head;
        if (h != null && h != tail) {
            int ws = h.waitStatus;
            //節點狀态為SIGNAL表示需要向後傳遞
            if (ws == Node.SIGNAL) {
                if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                    continue;            // 失敗了就loop
                unparkSuccessor(h);
            }
            //為0就設定成PROPAGETE表示需要傳播
            else if (ws == 0 &&
                     !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                continue;                // loop on failed CAS
        }
        if (h == head)                   // loop if head changed
            break;
    }
}
           

這裡最重要的是了解doReleaseShared,一個程序 doReleaseShared,然後unparkSuccessor,這時候被喚醒的其他線程可能線程切換運作,重新請求修改head節點,沒機會的話,這裡檢查head節點沒有變化就繼續for,一直等到被喚醒的線程時間片切換到,然後再将繼續修改下一個節點,到最後隊列中的所有節點都被喚醒。

這裡一定要想着線程切換多看看幾遍,我當時就郁悶了半天這個疑問。

對應的acquireSharedInterruptibly響應中斷和tryAcquireSharedNanos響應中斷逾時,跟獨占的都差不多。

共享模式release,這個沒什麼好說的,釋放判斷成功就doReleaseShared,把隊列中所有節點release

public final boolean releaseShared(int arg) {
    if (tryReleaseShared(arg)) {
        doReleaseShared();
        return true;
    }
    return false;
}
           

共享跟獨占的流程圖差不多,不想畫了,改天學習下AQS的condition。

參考:

http://www.infoq.com/cn/articles/java8-abstractqueuedsynchronizer

繼續閱讀