java多線程之五——JUC核心AbstractQueuedSynchronizer(AQS)源碼分析

本文基于java version "1.8.0_77"

閱讀本文章之前,你需要了解LockSupport中相關(guān)方法的介紹。
閱讀本篇文章,請對照源碼閱讀,否則可能云里霧里不知所云。

簡介

AbstractQueuedSynchronizer:譯為:隊(duì)列同步器(以下簡稱AQS),可以看到這是一個抽象類。有大名鼎鼎的并發(fā)大師Doug Lea設(shè)計:


image.png

并發(fā)包中很多Lock都是通過繼承AQS實(shí)現(xiàn)的(ReentrantLock、
ReentrantReadWriteLock和CountDownLatch等),AQS中封裝了實(shí)現(xiàn)鎖的具體操作,其子類繼承AQS后,可以輕松的調(diào)用AQS的相應(yīng)方法來實(shí)現(xiàn)同步狀態(tài)的管理同步狀態(tài),線程的排隊(duì),等待以及喚醒等操作。
子類可以重寫的方法如下:

  • protected boolean tryAcquire(int arg)獨(dú)占式的獲取同步狀態(tài),使用CAS設(shè)置同步狀態(tài)
  • protected boolean tryRelease(int arg)獨(dú)占式的釋放同步狀態(tài)
  • protected int tryAcquireShared(int arg)共享式的獲取同步狀態(tài),返回大于等于0的值,表示獲取成功,否則失敗
  • protected boolean tryReleaseShared(int arg)
    共享式的釋放同步狀態(tài)
  • protected boolean isHeldExclusively()判斷當(dāng)前是否被當(dāng)前線程鎖獨(dú)占

構(gòu)成

image.png

如上圖,AQS中定義了一個volatile整數(shù)狀態(tài)信息,我們可以通過getState(),setState(int newState),compareAndSetState(int expect,int update))等protected方法進(jìn)行操作這一狀態(tài)信息。例如:ReentrantLock中用它來表示所有線程呢個已經(jīng)重復(fù)獲取該鎖的次數(shù),Semaphore用它來表示剩余的許可數(shù)量,F(xiàn)utureTask用它來表示任務(wù)的狀態(tài)(未開始,正在運(yùn)行,已結(jié)束,已取消等)。

AQS是由一個同步隊(duì)列(FIFO雙向隊(duì)列)來管理同步狀態(tài)的,如果線程獲取同步狀態(tài)失敗,AQS會將當(dāng)前線程以及等待狀態(tài)信息構(gòu)造成一個節(jié)點(diǎn)(Node)加入到同步隊(duì)列中,同時阻塞當(dāng)前線程;當(dāng)同步狀態(tài)狀態(tài)釋放時,會把首節(jié)點(diǎn)中的線程喚醒,使其再次嘗試獲取同步狀態(tài)。

image.png

準(zhǔn)備工作

在跟著源碼走流程之前,我們先了一下以下幾個需要用到的概念:

AQS.Node

隊(duì)列示意圖如下:


image.png

每個Node節(jié)點(diǎn)都是一個自旋鎖:在阻塞時不斷循環(huán)讀取狀態(tài)變量,當(dāng)前驅(qū)節(jié)點(diǎn)釋放同步對象使用權(quán)后,跳出循環(huán),執(zhí)行同步代碼。我們在接下來的代碼分析中,也能夠看到通過死循環(huán)來達(dá)到自旋這一目的。

我們看一下Node節(jié)點(diǎn)類的幾個關(guān)鍵屬性(不必記住,下面用到的時候,再回來看即可):

MODE(兩個)


兩種Mode,用于創(chuàng)建Node時的構(gòu)造函數(shù)使用。在private Node addWaiter(Node mode)這一方法調(diào)用的時候傳入,用于想等待隊(duì)列中添加節(jié)點(diǎn)。

volatile int waitStatus

手機(jī)是waitStatus,用來表示當(dāng)前節(jié)點(diǎn)的狀態(tài)。其取值范圍如下:

  • static final int CANCELLED = 1;表示節(jié)點(diǎn)的線程是已被取消的。當(dāng)前節(jié)點(diǎn)由于超時或者被中斷而被取消。一旦節(jié)點(diǎn)被取消后,那么它的狀態(tài)值不在會被改變,且當(dāng)前節(jié)點(diǎn)的線程不會再次被阻塞。

  • static final int SIGNAL= -1;表示當(dāng)前節(jié)點(diǎn)的后繼節(jié)點(diǎn)的線程需要被喚醒。當(dāng)前節(jié)點(diǎn)的后繼節(jié)點(diǎn)已經(jīng) (或即將)被阻塞(通過LockSupport.park()) , 所以當(dāng) 當(dāng)前節(jié)點(diǎn)釋放或則被取消時候,一定要unpark它的后繼節(jié)點(diǎn)。為了避免競爭,獲取方法一定要首先設(shè)置node為signal,然后再次重新調(diào)用獲取方法,如果失敗,則阻塞。

  • static final int CONDITION = -2;表示線程正在等待某個條件。表示當(dāng)前節(jié)點(diǎn)正在條件隊(duì)列(AQS下的ConditionObject里也維護(hù)了個隊(duì)列)中,在從conditionObject隊(duì)列轉(zhuǎn)移到同步隊(duì)列前,它不會在同步隊(duì)列(AQS下的隊(duì)列)中被使用。當(dāng)成功轉(zhuǎn)移后,該節(jié)點(diǎn)的狀態(tài)值將由CONDITION設(shè)置為0。

  • static final int PROPAGATE = -3;表示下一個共享模式的節(jié)點(diǎn)應(yīng)該無條件的傳播下去。共享模式下的釋放操作應(yīng)該被傳播到其他節(jié)點(diǎn)。該狀態(tài)值在doReleaseShared方法中被設(shè)置的。

  • 0 以上都不是。

可以看到,非負(fù)數(shù)值(0和已經(jīng)取消)意味著該節(jié)點(diǎn)不需要被喚醒。所以,大多數(shù)代碼中不需要檢查該狀態(tài)值的確定值,只需要根據(jù)正負(fù)值來判斷即可對于一個正常的Node,他的waitStatus初始化值時0。對于一個condition隊(duì)列中的Node,他的初始化值時CONDITION。如果想要修改這個值,可以使用AQS提供CAS進(jìn)行修改。(方法:boolean compareAndSetWaitStatus(Node node, int expect,int update)

volatile Node prev

用于鏈接當(dāng)前節(jié)點(diǎn)的前驅(qū)節(jié)點(diǎn),當(dāng)前節(jié)點(diǎn)依賴前驅(qū)節(jié)點(diǎn)來檢測waitStatus,前驅(qū)節(jié)點(diǎn)是在當(dāng)前節(jié)點(diǎn)入隊(duì)時候被設(shè)置的。為了提高GC效率,在當(dāng)前節(jié)點(diǎn)出隊(duì)時候會把前驅(qū)節(jié)點(diǎn)設(shè)置為null。而且,在取消前驅(qū)節(jié)點(diǎn)的時候,則會while循環(huán)直到找到一個非取消(cancelled)的節(jié)點(diǎn),由于頭節(jié)點(diǎn)永遠(yuǎn)不會是取消狀態(tài),所以我們一定可以找到非取消狀態(tài)的前置節(jié)點(diǎn)。

volatile Node next;

用于鏈接當(dāng)前節(jié)點(diǎn)的后繼節(jié)點(diǎn),在當(dāng)前節(jié)點(diǎn)釋放時候會喚醒后繼節(jié)點(diǎn)。在一個當(dāng)前節(jié)點(diǎn)入隊(duì)的時候,會先設(shè)置當(dāng)前節(jié)點(diǎn)的prev,而不會立即設(shè)置前置節(jié)點(diǎn)的next。而是用CAS替換了tail之后才設(shè)置前置節(jié)點(diǎn)的next。(方法Node addWaiter(Node mode))

Node nextWaiter

用來串聯(lián)條件隊(duì)列,連接到下一個在條件上等待的結(jié)點(diǎn)或是特殊的值SHARED。因?yàn)闂l件隊(duì)列只在獨(dú)占模式下持有時訪問,我們只需要一個簡單的鏈表隊(duì)列來持有在條件上等待的結(jié)點(diǎn)。再然后它們會被轉(zhuǎn)移到同步隊(duì)列(AQS隊(duì)列)再次重新獲取。由于條件隊(duì)列只能在獨(dú)占模式下使用,所以我們要表示共享模式的節(jié)點(diǎn)的話只要使用特殊值SHARED來標(biāo)明即可。

輔助方法分析(供查閱)

shouldParkAfterFailedAcquire

這個方法是信號控制(waitStatus)的核心。在獲取同步狀態(tài)失敗,生成Node并加入隊(duì)列中后,用于檢查和更新結(jié)點(diǎn)的狀態(tài)。返回true表示當(dāng)前節(jié)點(diǎn)應(yīng)該被阻塞。

private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        int ws = pred.waitStatus;
        if (ws == Node.SIGNAL)
            /*
             * 前驅(qū)節(jié)點(diǎn)如果狀態(tài)如果為SIGNAL。表明當(dāng)前節(jié)點(diǎn)應(yīng)被阻塞,等待喚醒(參見上文的SIGNAL狀態(tài))
             * 則返回true,然后park掛起線程
             */
            return true;
        if (ws > 0) {
            /*
             * 前驅(qū)節(jié)點(diǎn)狀態(tài)值大于0(只有一個取值1),表示前驅(qū)節(jié)點(diǎn)已經(jīng)取消
             * 此時應(yīng)該丟棄前驅(qū)節(jié)點(diǎn),而繼續(xù)尋找前驅(qū)節(jié)點(diǎn)的前驅(qū)節(jié)點(diǎn),(見下圖)
             * 這里使用while循環(huán)查找前驅(qū)節(jié)點(diǎn),并將當(dāng)前節(jié)點(diǎn)的prev屬性設(shè)置為找到的新的節(jié)點(diǎn)。(下圖步驟1)
             * 并將新的前驅(qū)節(jié)點(diǎn)的后繼節(jié)點(diǎn)設(shè)置為當(dāng)前節(jié)點(diǎn)(下圖步驟2)
             */
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node;
        } else {
            /*
             * 排除以上SIGNAL(-1)和>0(1)兩種情況
             * 現(xiàn)在是前驅(qū)節(jié)點(diǎn)的waitStatus為0或PROPAGATE(-3)的情況(不考慮CONDITION的情況)
             * 這時候表明前驅(qū)節(jié)點(diǎn)需要重新設(shè)置waitStatus
             * 這樣在下一輪循環(huán)中,就可以判斷前驅(qū)節(jié)點(diǎn)的SIGNAL而阻塞park當(dāng)前節(jié)點(diǎn),以便于等待前驅(qū)節(jié)點(diǎn)的unpark(比如:shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())
             */
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
    }

如圖:


image.png

parkAndCheckInterrupt

與上面的shouldParkAfterFailedAcquire中聯(lián)合調(diào)用

(shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())

通過shouldParkAfterFailedAcquire方法獲取到可用的前驅(qū)節(jié)點(diǎn),并設(shè)置前驅(qū)節(jié)點(diǎn)的WaitStatus值為SIGNAL,進(jìn)而在此方法中將當(dāng)前線程park(阻塞等待)。線程醒了之后,檢查線程是否被重點(diǎn),并將結(jié)果返回。

cancelAcquire

上面講到,每一個NODE節(jié)點(diǎn)都是一個自旋鎖,都在不斷進(jìn)行死循環(huán)自旋,當(dāng)自旋過程中發(fā)生異常而無法獲得鎖,就需要取消節(jié)點(diǎn)。
需要做的是:

  • 清空node節(jié)點(diǎn)中的引用
  • node出隊(duì):剔除當(dāng)前節(jié)點(diǎn),打斷next和prev引用。分為三種情況:1. node是tail 2. node既不是tail,也不是head的后繼節(jié)點(diǎn) 3. node是head的后繼節(jié)點(diǎn)
    源碼分析如下:
 private void cancelAcquire(Node node) {
        // 如果node為空,忽略,直接返回
        if (node == null)
            return;
        
        //將thread引用置空
        node.thread = null;

        // 跳過取消的(cancelled)的前置節(jié)點(diǎn),找到一個有效的前驅(qū)節(jié)點(diǎn),如上面分析過的shouldParkAfterFailedAcquire
        Node pred = node.prev;
        while (pred.waitStatus > 0)
            node.prev = pred = pred.prev;

        // 拿到前驅(qū)節(jié)點(diǎn)的后繼節(jié)點(diǎn)
        Node predNext = pred.next;

        // 將節(jié)點(diǎn)的狀態(tài)值設(shè)為已取消,這樣,其他節(jié)點(diǎn)就可以跳過本節(jié)點(diǎn),而不受其他線程的干擾
        node.waitStatus = Node.CANCELLED;

        // 情況1:如果當(dāng)前節(jié)點(diǎn)是尾節(jié)點(diǎn),CAS替換tail字段的引用為為前驅(qū)節(jié)點(diǎn)
        // 成功之后,CAS將前驅(qū)節(jié)點(diǎn)的后繼節(jié)點(diǎn)置空
        if (node == tail && compareAndSetTail(node, pred)) {
            compareAndSetNext(pred, predNext, null);
        } else {
            // 情況2:如果當(dāng)前節(jié)點(diǎn)不是tail,而前驅(qū)節(jié)點(diǎn)又不是head
            // 則嘗試CAS將前驅(qū)節(jié)點(diǎn)的waitStatus標(biāo)記為SIGNAL(表示前驅(qū)節(jié)點(diǎn)的后繼節(jié)點(diǎn)需要喚醒)
            // 設(shè)置成功之后,CAS將前驅(qū)節(jié)點(diǎn)的后繼節(jié)點(diǎn)設(shè)置為當(dāng)前節(jié)點(diǎn)的后繼節(jié)點(diǎn)(將當(dāng)前節(jié)點(diǎn)剔除)
            int ws;
            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 {
            // 情況3:如果node是head的后繼節(jié)點(diǎn),則直接喚醒node的后繼節(jié)點(diǎn)
                unparkSuccessor(node);
            }

            node.next = node; // help GC
        }
    }

如上:
情況1:


image.png
  • 1:compareAndSetTail(node, pred) 替換tail的引用
  • 2:compareAndSetNext(pred, predNext, null); 將pred的next置空

情況2:


image.png
  • compareAndSetNext(pred, predNext, next); 將前驅(qū)節(jié)點(diǎn)的next指向后繼節(jié)點(diǎn)。后繼節(jié)點(diǎn)的prev將在前面講過的shouldParkAfterFailedAcquire進(jìn)行添加。

情況3
下面將分析unparkSuccessor方法


unparkSuccessor

用于喚醒當(dāng)前節(jié)點(diǎn)的后繼節(jié)點(diǎn)。

private void unparkSuccessor(Node node) {
       // 將當(dāng)前節(jié)點(diǎn)的狀態(tài)重置
        int ws = node.waitStatus;
        if (ws < 0)
            compareAndSetWaitStatus(node, ws, 0);

        // 拿到后繼節(jié)點(diǎn) ,如果后繼機(jī)節(jié)點(diǎn)是空或標(biāo)記為取消(cancelled)
        // 開是循環(huán)獲取后繼的可用節(jié)點(diǎn)
        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;
        }
        // LockSupport喚醒下一個節(jié)點(diǎn)
        if (s != null)
            LockSupport.unpark(s.thread);
    }

上文中尋找下一個可用節(jié)點(diǎn)的時候,可以看到不是head->tail尋找,而是tail->head倒序?qū)ふ遥@是因?yàn)椋和ㄟ^上面代碼可以看到,只有在當(dāng)前節(jié)點(diǎn)node的后繼節(jié)點(diǎn)為nul的時候,才會執(zhí)行循環(huán)尋找后面的可用后繼節(jié)點(diǎn)。注意此處:后繼節(jié)點(diǎn)已經(jīng)為null了,故只能從尾部向前遍歷,找到第一個可用節(jié)點(diǎn)。

差不多就這些了,下面我們進(jìn)入正題,探討一下獲取同步化狀態(tài)的流程。

-----------------------------------------------------

源碼分析

獨(dú)占式獲取同步狀態(tài)

上源碼:


首先tryAcquire(arg),tryAcquire是由子類實(shí)現(xiàn),通過操作state進(jìn)行判定當(dāng)前是否允許當(dāng)前線程獲取執(zhí)行權(quán)力,用來控制當(dāng)前是否允許獲取同步狀態(tài)。true表示獲取同步狀態(tài),不必加入同步隊(duì)列中。如果返回了false,沒有獲取同步狀態(tài),則需要加入到同步隊(duì)列中。繼續(xù)往下執(zhí)行:

addWaiter(Node mode)

首先將節(jié)點(diǎn)添加到等待隊(duì)列中:

private Node addWaiter(Node mode) {
        // 構(gòu)造一個Node,nextWaiter為null
        Node node = new Node(Thread.currentThread(), mode);
        // 獲取到tail節(jié)點(diǎn)(也就是接下來,當(dāng)前節(jié)點(diǎn)的前驅(qū)節(jié)點(diǎn))
        Node pred = tail;
        if (pred != null) {
            node.prev = pred;
            // CAS嘗試替換tail引用,如果成功,則返回
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
        // 上述不成功,存在多線程競爭,則自旋
        enq(node);
        return node;
    }

enq(final Node node)

private Node enq(final Node node) {
        for (;;) {
            Node t = tail;
            // 如果隊(duì)列為空,先CAS設(shè)置一下head空節(jié)點(diǎn),完事之后進(jìn)行下一次循環(huán)
            if (t == null) { 
                if (compareAndSetHead(new Node()))
                    tail = head;
            } else {
                // 設(shè)置當(dāng)前節(jié)點(diǎn)的prev,然后CAS設(shè)置設(shè)置tail,和前驅(qū)節(jié)點(diǎn)的next
                node.prev = t;
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    return t;
                }
            }
        }
    }

添加隊(duì)列成功之后,我們繼續(xù)往下看,還是那張圖


acquireQueued(final Node node, int arg)

acquireQueued主要是處理正在排隊(duì)等待的線程。自旋、阻塞重試獲取。如果獲取成功則替換當(dāng)前節(jié)點(diǎn)為鏈表頭,然后返回。在獲取過程中,忽略了中斷,但將是否中斷的返回了。

final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            // 死循環(huán)自旋,不斷嘗試獲取同步狀態(tài)
            for (;;) {
                //獲取當(dāng)前節(jié)點(diǎn)的前驅(qū)節(jié)點(diǎn)
                final Node p = node.predecessor();
                // 只有前驅(qū)節(jié)點(diǎn)是head,也就是說排隊(duì)排到當(dāng)前借錢,才有可能獲取同步狀態(tài)
                // 如果允許獲取同步狀態(tài),則將當(dāng)前節(jié)點(diǎn)設(shè)置為head,設(shè)置其他標(biāo)記,并返回,終止自旋
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                // 在上面同步獲取失敗后,有可能不是頭節(jié)點(diǎn)的后繼節(jié)點(diǎn),這時沒有資格獲取同步狀態(tài),就需要休眠
                // 下面代碼上面講過,進(jìn)一步檢查和更新節(jié)點(diǎn)狀態(tài),判斷當(dāng)前節(jié)點(diǎn)是否需要park,減少占用CPU,等待前驅(qū)節(jié)點(diǎn)釋放同步狀態(tài)將它喚醒
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            // 如果失敗,取消獲取同步狀態(tài),移除節(jié)點(diǎn),上文已講
            if (failed)
                cancelAcquire(node);
        }
    }

selfInterrupt

獲取鎖過程中,忽略了中斷,在這里處理中斷

static void selfInterrupt() {
        Thread.currentThread().interrupt();
    }

獲取分析完了,我們看一下,同步代碼執(zhí)行完畢,同步狀態(tài)是如何釋放的。

獨(dú)占式釋放同步狀態(tài)

 public final boolean release(int arg) {
        //首先調(diào)用子類重寫方法tryRelease,返回true標(biāo)識標(biāo)識允許釋放同步狀態(tài)
        if (tryRelease(arg)) {
            //如果允許釋放,則當(dāng)前head即為要釋放的node,只需要喚醒后繼node即可, unparkSuccessor上文講過
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }

到此,我們走完了獨(dú)占式鎖的獲取與釋放。簡要概述一下步驟:

  • 嘗試獲取鎖,如果不能獲取,添加進(jìn)隊(duì)列
  • 隊(duì)列中該node進(jìn)行自旋排隊(duì),嘗試獲取同步狀態(tài)
  • 如果當(dāng)前節(jié)點(diǎn)不是head的下個節(jié)點(diǎn),休眠,等待喚醒
  • 喚醒后,檢查自身是否已被interrupted,繼續(xù)嘗試獲取鎖
  • 獲取后,執(zhí)行同步代碼,
  • 執(zhí)行完畢后,release鎖,喚醒下個節(jié)點(diǎn)

-----------------------------------------------------

共享式獲取同步狀態(tài)

上源碼:



首先還是調(diào)用子類實(shí)現(xiàn)的tryAcquireShared,查看是否允許獲取同步狀態(tài)。如果首次獲取結(jié)果大于等于0.則完成獲取 。如果小于0,則表示不允許獲取同步狀態(tài),進(jìn)入隊(duì)列。

doAcquireShared(int arg)

死循環(huán)自旋嘗試獲取鎖

 private void doAcquireShared(int arg) {
        // 構(gòu)造Node,添加到隊(duì)列中,模式為Node.SHARED,查看Node構(gòu)造函數(shù)
        // 可以看到,當(dāng)前Node的nextWaiter(不是next,詳看上文)為一個空node對象
        final Node node = addWaiter(Node.SHARED);
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                // 拿到前驅(qū)node
                final Node p = node.predecessor();
                // 前驅(qū)node是head才有可能獲取鎖
                if (p == head) {
                    int r = tryAcquireShared(arg);
                    if (r >= 0) {   // tryAcquireShared大于等于0,允許獲取鎖
                       // 獲取成功,需要將當(dāng)前節(jié)點(diǎn)設(shè)置為AQS隊(duì)列中的第一個節(jié)點(diǎn)
                       // 這是AQS的規(guī)則,隊(duì)列的頭節(jié)點(diǎn)表示正在獲取鎖的節(jié)點(diǎn)
                       // 下面講解
                        setHeadAndPropagate(node, r);
                        p.next = null; // help GC
                        // 同獨(dú)占式
                        if (interrupted)
                            selfInterrupt();
                        failed = false;
                        return;
                    }
                }
                // 不解釋,見上文
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            // 不解釋,見上文
            if (failed)
                cancelAcquire(node);
        }
    }

setHeadAndPropagate

這是

private void setHeadAndPropagate(Node node, int propagate) {
        // 取到head做緩存
        Node h = head; 
        //將當(dāng)前節(jié)點(diǎn)設(shè)置為head
        setHead(node);
        // propagate是tryAcquireShared返回的值 ,可以理解為Semaphore,是否還允許其他并發(fā)
        if (propagate > 0 || h == null || h.waitStatus < 0 ||
            (h = head) == null || h.waitStatus < 0) {
            Node s = node.next;
            // 并檢查當(dāng)前節(jié)點(diǎn)的后繼節(jié)點(diǎn)為空或者后繼節(jié)點(diǎn)的nextWaiter是否為SHARED,表明后繼節(jié)點(diǎn)需要共享傳遞
            if (s == null || s.isShared())
                doReleaseShared();  // 進(jìn)行share傳遞, doReleaseShared
        }
    }

可以看到這里與獨(dú)占式的做了相似的事情,都進(jìn)行了設(shè)置head之后,區(qū)別是共享式獲取同步狀態(tài)又進(jìn)行了share傳遞,傳遞給下一個nextWaiter屬性同樣為SHAREED的節(jié)點(diǎn),我們看一下doReleaseShared方法

doReleaseShared

private void doReleaseShared() {
 /*
         * 即使在并發(fā),多個線程在獲取、釋放的情況下,確保釋放的傳播性,
         * 如果當(dāng)前節(jié)點(diǎn)標(biāo)記為SIGNAL(表示后繼節(jié)點(diǎn)需要喚醒,按理說應(yīng)該在當(dāng)前節(jié)點(diǎn)釋放的時候喚醒,但是此處是共享模式,故立即喚醒),則通常嘗試頭節(jié)點(diǎn)的unparkSuccessor 動作。
         * 但是如果他不符合喚醒的條件,為了確保能正確release,那么則把head的waitState設(shè)置為為PROPAGATE
         * 此外,在執(zhí)行該代碼時,為了以防萬一有新
         * 節(jié)點(diǎn)的加入,或者我們CAS修改失敗,所以我們的更新需要在循環(huán)中,不斷嘗試。
         */
        for (;;) {
            Node h = head;
            if (h != null && h != tail) {
                int ws = h.waitStatus;
                if (ws == Node.SIGNAL) {
                    if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                        continue;            // 失敗了就繼續(xù)loop  
                    unparkSuccessor(h);
                }
                else if (ws == 0 &&
                         !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                    continue;                // 失敗了就繼續(xù)loop  
            }
            if (h == head)                   // loop if head changed
                break;
        }
    }

這里最重要的是要多線程環(huán)境中理解doReleaseShared,一個線程A執(zhí)行doReleaseShared,然后unparkSuccessor,線程B喚醒執(zhí)行,這時候被喚醒的線程B運(yùn)行,重新請求獲取同步狀態(tài),修改head節(jié)點(diǎn),喚醒線程C,然后依次喚醒D、E、F……每個節(jié)點(diǎn)在自己喚醒的同時,也喚醒了后面的節(jié)點(diǎn),設(shè)置為head,這樣就達(dá)到了共享模式。

注意h == head,我們看到上面有注釋說Additionally, we must loop in case a new node is added while we are doing this.為了避免在執(zhí)行到這里的時候。如果有兩個新的節(jié)點(diǎn)添加到隊(duì)列中來,一個節(jié)點(diǎn)A喚醒B之后,B恰好setHead了,此時head是B節(jié)點(diǎn)。此時A之前獲得的head并不是新的head了,故需要繼續(xù)循環(huán),以盡可能保證成功性。

可以看到 獨(dú)占式與共享式的差別就是共享的傳遞:
獨(dú)占模式喚醒頭節(jié)點(diǎn),頭節(jié)點(diǎn)釋放之后,后繼節(jié)點(diǎn)喚醒
共享模式喚醒全部節(jié)點(diǎn)。

共享式釋放同步狀態(tài)

源碼不貼了,調(diào)用的是上述的doReleaseShared()

響應(yīng)中斷獲取鎖

acquireInterruptibly和acquire差不多,acquireSharedInterruptibly和acquireShared差不多,區(qū)別就是拋出了InterruptedException。

-----------------------完畢-------------------------

下一篇繼續(xù)擼Reentrantlock
本人能力有限,分析的不夠的地方,還望多多指正。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,702評論 6 534
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,615評論 3 419
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,606評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,044評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,826評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,227評論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,307評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,447評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,992評論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,807評論 3 355
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,001評論 1 370
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,550評論 5 361
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,243評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,667評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,930評論 1 287
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,709評論 3 393
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,996評論 2 374

推薦閱讀更多精彩內(nèi)容