Java并發(fā)-locks包源碼剖析2-ReentrantLock鎖機(jī)制

上一篇文章對ReentrantLock鎖進(jìn)行了概述斧抱,相信看完了的話應(yīng)該對ReentrantLock鎖的使用有了一定的了解常拓,這篇文章分析下ReentrantLock鎖的實(shí)現(xiàn)機(jī)制。

\color{blue}{1\ Sync, NonfairSync, FairSync}

首先需要了解ReentrantLock類里面有三個靜態(tài)類:Sync夺姑、NonfairSyncFairSync墩邀,ReentrantLock的鎖內(nèi)部實(shí)現(xiàn)通過NonfairSync和FairSync實(shí)現(xiàn),而NonfairSync和FairSync都是Sync的子類盏浙。類圖如下所示:

類圖中有一個抽象類AbstractQueuedSynchronizer眉睹,類如其名,抽象的隊列式的同步器废膘,通常被稱之為AQS類竹海,它是一個非常有用的父類,可用來定義鎖以及依賴于排隊park線程(這里的park你可能不明白什么意思丐黄,但是你肯定知道阻塞的意思斋配,其實(shí)park類似阻塞,很多文章直接就說是阻塞,但是我感覺兩者還是不一樣的艰争,park可以看做是一種輕量級的阻塞坏瞄,至少我是這樣理解的,歡迎指正甩卓,unpark類似喚醒線程但是比喚醒線程高效)的其他同步器鸠匀。AbstractQueuedLongSynchronizer 類提供相同的功能但擴(kuò)展了對同步狀態(tài)的 64 位的支持。兩者都擴(kuò)展了類 AbstractOwnableSynchronizer(一個幫助記錄當(dāng)前保持獨(dú)占同步的線程的簡單類)逾柿。AQS框架是整個JUC鎖的核心部分缀棍,提供了以下內(nèi)容與功能:

  1. Node 節(jié)點(diǎn), Sync Queue和Condition Queue的存放的元素,用來保存park線程, 這些節(jié)點(diǎn)主要的區(qū)分在于 waitStatus 的值
  2. Condition Queue, 這個隊列是用于獨(dú)占模式中, 只有用到 Condition.awaitXX 時才會將 node加到 tail 上(PS: 在使用 Condition的前提是已經(jīng)獲取 Lock)
  3. Sync Queue, 獨(dú)占和共享的模式中均會使用到的存放 Node 的 CLH queue(主要特點(diǎn)是, 隊列中總有一個 dummy 節(jié)點(diǎn), 后繼節(jié)點(diǎn)獲取鎖的條件由前繼節(jié)點(diǎn)決定, 前繼節(jié)點(diǎn)在釋放 lock 時會喚醒sleep中的后繼節(jié)點(diǎn))机错,CLH隊列的CLH全稱是Craig, Landin, and Hagersten
  4. ConditionObject, 用于獨(dú)占的模式, 主要是線程釋放lock, 加入 Condition Queue, 并進(jìn)行相應(yīng)的 signal 操作
  5. 獨(dú)占的獲取lock (acquire, release), 例如 ReentrantLock 就是使用這種,
  6. 共享的獲取lock (acquireShared, releaseShared), 例如 ReentrantReadWriteLock爬范、Semaphore、CountDownLatch

所以弱匪,理解了AbstractQueuedSynchronizer 機(jī)制就理解了ReentrantLock鎖機(jī)制青瀑。

\color{blue}{2\ AbstractQueuedSynchronizer}

AbstractQueuedSynchronizer繼承自AbstractOwnableSynchronizer

public abstract class AbstractOwnableSynchronizer implements java.io.Serializable {
    /**所有的變量的都是transient的,為啥還要提供序列化ID呢痢法?*/
    private static final long serialVersionUID = 3737899427754241961L;
    protected AbstractOwnableSynchronizer() { }
    // The current owner of exclusive mode synchronization.
    private transient Thread exclusiveOwnerThread;
    protected final void setExclusiveOwnerThread(Thread thread) {
        exclusiveOwnerThread = thread;
    }
    protected final Thread getExclusiveOwnerThread() {
        return exclusiveOwnerThread;
    }
}

AbstractOwnableSynchronizer類名能夠很清晰的表達(dá)它的意思:獨(dú)有線程同步器狱窘,提供了線程獨(dú)占的兩個方法setExclusiveOwnerThread(Thread thread)getExclusiveOwnerThread()

\color{blue}{2.1\ AbstractQueuedSynchronizer}內(nèi)部類 Node

AQS有一個很重要的內(nèi)部類Node,這個類會裝載線程财搁,看下它的源碼:

    static final class Node {
        /** Marker to indicate a node is waiting in shared mode */
        static final Node SHARED = new Node();
        /** Marker to indicate a node is waiting in exclusive mode */
        static final Node EXCLUSIVE = null;
        /** waitStatus value to indicate thread has cancelled */
        static final int CANCELLED =  1;
        /** waitStatus value to indicate successor's thread needs unparking */
        static final int SIGNAL    = -1;
        /** waitStatus value to indicate thread is waiting on condition */
        static final int CONDITION = -2;
        /**
         * waitStatus value to indicate the next acquireShared should
         * unconditionally propagate
         */
        static final int PROPAGATE = -3;

        volatile int waitStatus;
        volatile Node prev;
        volatile Node next;
        volatile Thread thread;

        Node nextWaiter;

        final boolean isShared() {//Returns true if node is waiting in shared mode.
            return nextWaiter == SHARED;
        }

        final Node predecessor() throws NullPointerException {//
            Node p = prev;
            if (p == null)
                throw new NullPointerException();
            else
                return p;
        }

        Node() {    // Used to establish initial head or SHARED marker
        }

        Node(Thread thread, Node mode) {     // Used by addWaiter
            this.nextWaiter = mode;
            this.thread = thread;
        }

        Node(Thread thread, int waitStatus) { // Used by Condition
            this.waitStatus = waitStatus;
            this.thread = thread;
        }
    }

可以看到蘸炸,Node類里首先定義了6個static final的變量,用來區(qū)分是共享鎖還是獨(dú)占鎖以及等待線程的五種狀態(tài)(1尖奔、-1搭儒、-2、-3再額外加一個0的狀態(tài))提茁,接著定義了四個volatile變量和一個Node類型的普通變量nextWaiter: Node淹禾。我們需要著重分析下這四個volatile變量和nextWaiter變量。

  1. volatile int waitStatus:
    • CANCELLED值為1表示當(dāng)前節(jié)點(diǎn)裝載的線程因超時或者中斷而取消茴扁,Nodes never leave this state. In particular, a thread with cancelled node never again blocks.
    • SIGNAL值為-1表示后繼節(jié)點(diǎn)被park了铃岔,如果當(dāng)前節(jié)點(diǎn)釋放鎖了或者裝載的線程取消了,那么需要unpark自己的后繼節(jié)點(diǎn)峭火,To avoid races, acquire methods must first indicate they need a signal, then retry the atomic acquire, and then, on failure, block.
    • CONDITION值為-2表示在條件隊列(condition queue)中毁习,It will not be used as a sync queue node until transferred, at which time the status will be set to 0. (Use of this value here has nothing to do with the other uses of the field, but simplifies mechanics.)
    • PROPAGATE值為-3表示后續(xù)的acquireShared操作能夠得以執(zhí)行,A releaseShared should be propagated to other nodes. This is set (for head node only) in doReleaseShared to ensure propagation continues, even if other operations have since intervened.
    • 值為0表示當(dāng)前節(jié)點(diǎn)在同步隊列(async queue)中等待著獲取鎖
  2. volatile Node prev:前驅(qū)節(jié)點(diǎn)
  3. volatile Node next:后繼節(jié)點(diǎn)
  4. volatile Thread thread:當(dāng)前節(jié)點(diǎn)裝載的線程
  5. Node nextWaiter:這個后繼節(jié)點(diǎn)和后繼節(jié)點(diǎn)next是有區(qū)別的卖丸,nextWaiter后繼節(jié)點(diǎn)是指在條件隊列(condition queue)中的后繼節(jié)點(diǎn)或者是共享節(jié)點(diǎn)纺且,因?yàn)闂l件鎖必須是獨(dú)占模式,在共享模式中我們也可以使用這個成員變量表示后繼節(jié)點(diǎn)節(jié)點(diǎn)稍浆,這樣我們就節(jié)省了一個成員變量载碌。注意只有nextWaiter而并沒有定義preWaiter猜嘱。Link to next node waiting on condition, or the special value SHARED. Because condition queues are accessed only when holding in exclusive mode, we just need a simple linked queue to hold nodes while they are waiting on conditions. They are then transferred to the queue to re-acquire. And because conditions can only be exclusive, we save a field by using special value to indicate shared mode.

從上面我們可以看出,同步隊列async queue是一個雙向隊列嫁艇,而條件隊列condition queue是一個單向隊列只有nextWaiter而沒有preWaiter朗伶。

關(guān)于waitStatus這個變量,源碼中的注釋寫的非常清楚步咪,我不翻譯了腕让,大家細(xì)心看下:

The values are arranged numerically to simplify use. Non-negative values mean that a node doesn't need to signal. So, most code doesn't need to check for particular values, just for sign. The field is initialized to 0 for normal sync nodes, and CONDITION for condition nodes. It is modified using CAS (or when possible, unconditional volatile writes).

\color{blue}{2.2\ AbstractQueuedSynchronizer}內(nèi)部類 ConditionObject

這個類是用來維護(hù)條件隊列(condition queue)的,這個類和LinkedList很像歧斟,就是一個單向的列表,里面維護(hù)了一個頭結(jié)點(diǎn)firstWaiter和尾節(jié)點(diǎn)lastWaiter以及兩個十分重要的方法await()signal()偏形。先來看下這個類的一個很核心的await方法:

        public final void await() throws InterruptedException {
            //加入條件等待隊列前需要進(jìn)行中斷判斷
            if (Thread.interrupted())
                throw new InterruptedException();
            //加入條件等待隊列
            Node node = addConditionWaiter();
            //釋放鎖
            int savedState = fullyRelease(node);
            //中斷標(biāo)記位
            int interruptMode = 0;
            /*死循環(huán)阻塞静袖,直到被通知或者被中斷:
            1) 當(dāng)被通知喚醒時還得判斷一下當(dāng)前節(jié)點(diǎn)是否已經(jīng)轉(zhuǎn)移到AQS同步隊列當(dāng)中(其實(shí)主動通知的線程會確保其后繼等待節(jié)點(diǎn)轉(zhuǎn)移到同步隊列中,所以被通知后在下一次循環(huán)條件為false俊扭,繼續(xù)后續(xù)流程)队橙;
            2) 當(dāng)被中斷喚醒時需要確保節(jié)點(diǎn)被轉(zhuǎn)移到同步隊列中,然后根據(jù)中斷發(fā)生在被通知前后位置設(shè)置中斷模式萨惑,并跳出循環(huán)
            關(guān)于中斷模式:
             1) 當(dāng)在被通知前被中斷則將中斷模式設(shè)置為THROW_IE捐康;
             2) 當(dāng)在被通知后則將中斷模式設(shè)置為REINTERRUPT(因?yàn)閍cquireQueued不會響應(yīng)中斷)。
            */
            while (!isOnSyncQueue(node)) {
                LockSupport.park(this);
                if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
                    break;
            }
            /*死循環(huán)獲取同步狀態(tài)庸蔼,并在同步狀態(tài)獲取成功或者取消獲取時設(shè)置中斷模式:
            如果在被通知之后獲取鎖過程中發(fā)生中斷則將中斷模式設(shè)置為REINTERRUPT解总。
            */
            if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
                interruptMode = REINTERRUPT;
            //重新鏈接CONDITION節(jié)點(diǎn)。
            if (node.nextWaiter != null) // clean up if cancelled
                unlinkCancelledWaiters();
            //根據(jù)中斷模式拋出異常姐仅。
            if (interruptMode != 0)
                reportInterruptAfterWait(interruptMode);
        }

注意:被中斷的線程跳出while循環(huán)后花枫,會調(diào)用acquireQueued方法自旋獲取鎖,嘗試獲取同步狀態(tài)掏膏,而不是立即響應(yīng)中斷拋出中斷異常劳翰。在最后根據(jù)中斷模式來決定是否拋出異常。
再來看下signal()方法:

        //通知頭結(jié)點(diǎn)到同步隊列中去競爭鎖馒疹,用于獨(dú)占模式
        public final void signal() {
            if (!isHeldExclusively())
                throw new IllegalMonitorStateException();
            Node first = firstWaiter;
            if (first != null)
                doSignal(first);
        }
        /*
        從頭結(jié)點(diǎn)向后遍歷直到遇到一個非canceled或者null的節(jié)點(diǎn)
        并將其移除條件等待隊列佳簸,并添加到同步隊列的尾部
        */
        private void doSignal(Node first) {
            do {
                if ( (firstWaiter = first.nextWaiter) == null)
                    lastWaiter = null;
                first.nextWaiter = null;
            } 
        //當(dāng)頭結(jié)點(diǎn)轉(zhuǎn)移失敗后,繼續(xù)重試并確保更新后的頭結(jié)點(diǎn)不為null
        while (!transferForSignal(first) &&
                     (first = firstWaiter) != null);
        }

    final boolean transferForSignal(Node node) {
        //如果CAS失敗颖变,說明節(jié)點(diǎn)在signal之前被cancel了生均,返回false
        //CAS嘗試將節(jié)點(diǎn)的waitStatus從CONDITION-2修改為0
        if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
            return false;

        /*
         * Splice onto queue and try to set waitStatus of predecessor to
         * indicate that thread is (probably) waiting. If cancelled or
         * attempt to set waitStatus fails, wake up to resync (in which
         * case the waitStatus can be transiently and harmlessly wrong).
         */
        //進(jìn)入同步隊列,返回的p是這個節(jié)點(diǎn)在同步隊列中的前驅(qū)節(jié)點(diǎn)
        Node p = enq(node);
        //獲取前驅(qū)節(jié)點(diǎn)的狀態(tài)
        int ws = p.waitStatus;
        //1 如果前驅(qū)節(jié)點(diǎn)取消則直接喚醒當(dāng)前節(jié)點(diǎn)線程
        //2 或者前驅(qū)節(jié)點(diǎn)沒取消的話悼做,將前驅(qū)節(jié)點(diǎn)狀態(tài)設(shè)置為SIGNAL(-1)疯特,保證能被前驅(qū)節(jié)點(diǎn)通知到,如果設(shè)置失敗肛走,直接喚醒當(dāng)前節(jié)點(diǎn)的線程
        if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
            LockSupport.unpark(node.thread);
        return true;
    }

    private static final boolean compareAndSetWaitStatus(Node node,
                                                         int expect,
                                                         int update) {
        return unsafe.compareAndSwapInt(node, waitStatusOffset,
                                        expect, update);
    }

可以看到漓雅,更新waitStatus的操作是依賴Unsafe的CAS機(jī)制進(jìn)行的。
總結(jié):

  1. 每一個創(chuàng)建的ConditionObject都維持這各自的一個單向的等待隊列,但是每個ConditionObject都共享一個AQS的FIFO同步隊列邻吞,當(dāng)調(diào)用await方法時釋放鎖并進(jìn)入阻塞狀態(tài)组题,調(diào)用signal方法將條件等待隊列中的首節(jié)點(diǎn)線程移動到AQS同步隊列中并將其前繼節(jié)點(diǎn)設(shè)置為SIGNAL或者直接喚醒線程使得被通知的線程能去獲取鎖。
  2. 調(diào)用await方法釋放鎖并將線程添加到條件等待隊列中并沒有采用死循環(huán)CAS設(shè)置(參考AQS.enq方法)抱冷,因?yàn)镃ondition對象只能用于獨(dú)占模式崔列,而且在調(diào)用await之前會顯示的獲取獨(dú)占鎖,否則會拋出非法監(jiān)視器狀態(tài)異常旺遮。
  3. 調(diào)用signal方法將轉(zhuǎn)移等待節(jié)點(diǎn)赵讯,也不需要CAS來保證,因?yàn)閟ignal會確保調(diào)用者caller是獲取獨(dú)占鎖的線程(通過isHeldExclusively方法來判斷耿眉,如果為false會拋出非法監(jiān)視器狀態(tài)的異常)边翼。

\color{blue}{2.3\ }AQS的同步狀態(tài)

AQS的int型成員變量state表示同步狀態(tài),它是用volatile修飾:

private volatile int state;

在互斥鎖中它表示著線程是否已經(jīng)獲取了鎖鸣剪,0未獲取组底,1已經(jīng)獲取了,大于1表示重入數(shù)筐骇。同時AQS提供了getState()债鸡、setState()、compareAndSetState()方法來獲取和修改該值:

可重入鎖指的是在一個線程中可以多次獲取同一把鎖铛纬,比如:一個線程在執(zhí)行一個帶鎖的方法厌均,該方法中又調(diào)用了另一個需要相同鎖的方法,則該線程可以直接執(zhí)行調(diào)用的方法饺鹃,而無需重新獲得鎖莫秆。synchronized鎖可以看做重入鎖。但要注意悔详,獲取多少次鎖就要釋放多么次镊屎,這樣才能保證state是能回到零態(tài)的。

以ReentrantLock為例茄螃,state初始化為0缝驳,表示未鎖定狀態(tài)。A線程lock()時归苍,會調(diào)用tryAcquire()獨(dú)占該鎖并將state+1用狱。此后,其他線程再tryAcquire()時就會失敗拼弃,直到A線程unlock()到state=0(即釋放鎖)為止夏伊,其它線程才有機(jī)會獲取該鎖。當(dāng)然吻氧,釋放鎖之前溺忧,A線程自己是可以重復(fù)獲取此鎖的(state會累加)咏连。

所以可重入數(shù)大于1表示該線程可能調(diào)用了多個需要當(dāng)前鎖的方法,或同一個線程調(diào)用了多次lock()方法鲁森。

再以CountDownLatch以例祟滴,任務(wù)分為N個子線程去執(zhí)行,state也初始化為N(注意N要與線程個數(shù)一致)歌溉。這N個子線程是并行執(zhí)行的垄懂,每個子線程執(zhí)行完后countDown()一次,state會CAS減1痛垛。等到所有子線程都執(zhí)行完后(即state=0)草慧,會unpark()主調(diào)用線程,然后主調(diào)用線程就會從await()函數(shù)返回匙头,繼續(xù)后余動作冠蒋。

protected final int getState() {
    return state;
}

protected final void setState(int newState) {
    state = newState;
}

protected final boolean compareAndSetState(int expect, int update) {
    return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}

\color{blue}{2.4\ }同步隊列async queue(CLH)

CLH是一個FIFO線程等待隊列,多線程爭用資源被阻塞時會進(jìn)入此隊列乾胶,它的頭節(jié)點(diǎn)時虛擬節(jié)點(diǎn)。
CLH同步隊列(圖片來自引用4)

\color{blue}{2.5\ }獨(dú)占鎖acquire/release方法解析

對于獲取鎖的流程朽寞,這張圖比直接貼代碼更加直觀识窿,希望結(jié)合源碼仔細(xì)看下,圖片來自文獻(xiàn)3:
acquire獲取鎖流程
  1. "當(dāng)前線程"首先通過tryAcquire()(大部分由子類實(shí)現(xiàn))嘗試獲取鎖脑融。獲取成功的話喻频,直接返回;嘗試失敗的話肘迎,進(jìn)入到等待隊列排序等待(前面還有可能有需要線程在等待該鎖)甥温。
  2. "當(dāng)前線程"嘗試失敗的情況下,先通過addWaiter(Node.EXCLUSIVE)來將“當(dāng)前線程"加入到"CLH隊列"尾部妓布。CLH隊列就是線程等待隊列姻蚓。
  3. 執(zhí)行完addWaiter(Node.EXCLUSIVE)之后,調(diào)用acquireQueued方法獲取鎖匣沼,判斷當(dāng)前線程的Node == head狰挡,如果不是,調(diào)用park方法释涛,讓當(dāng)前線程進(jìn)入休眠狀態(tài)加叁,等待喚醒;
  4. "當(dāng)前線程"在執(zhí)行acquireQueued()時唇撬,會進(jìn)入到CLH隊列中休眠等待它匕,直到獲取鎖了才返回!如果"當(dāng)前線程"在休眠等待過程中被中斷過窖认,acquireQueued會返回true豫柬,此時"當(dāng)前線程"會調(diào)用selfInterrupt()來自己給自己產(chǎn)生一個中斷告希。至于為什么要自己給自己產(chǎn)生一個中斷,后面再介紹轮傍。
  5. 當(dāng)當(dāng)前的Node是頭的時候暂雹,再次回調(diào)tryAcquire,子類業(yè)務(wù)邏輯修改整個AQS隊列的state的狀態(tài)创夜, 設(shè)置為頭節(jié)點(diǎn)杭跪,next指針==null,方便GC驰吓,返回執(zhí)行對應(yīng)的業(yè)務(wù)邏輯涧尿;

對于鎖的釋放,源碼如下:

    public final boolean release(int arg) {
        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }

它調(diào)用tryRelease()來釋放資源檬贰,這個tryRelease()方法一般都由子類實(shí)現(xiàn)姑廉。
在release()中“當(dāng)前線程”釋放鎖成功的話,會喚醒當(dāng)前線程的后繼線程翁涤。根據(jù)CLH隊列的FIFO規(guī)則桥言,“當(dāng)前線程”(即已經(jīng)獲取鎖的線程)肯定是head;如果CLH隊列非空的話葵礼,則喚醒鎖的下一個等待線程号阿。看下unparkSuccessor()的源碼鸳粉,它在AQS中實(shí)現(xiàn):

    private void unparkSuccessor(Node node) {
        /*
         * If status is negative (i.e., possibly needing signal) try
         * to clear in anticipation of signalling.  It is OK if this
         * fails or if status is changed by waiting thread.
         */
        int ws = node.waitStatus;
        if (ws < 0)
            compareAndSetWaitStatus(node, ws, 0);

        /*
         * Thread to unpark is held in successor, which is normally
         * just the next node.  But if cancelled or apparently null,
         * traverse backwards from tail to find the actual
         * non-cancelled successor.
         */
        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;
        }
        if (s != null)
            LockSupport.unpark(s.thread);
    }

用unpark()喚醒等待隊列中最前邊的那個未放棄線程扔涧,這里我們也用s來表示吧。此時届谈,再和acquireQueued()聯(lián)系起來枯夜,s被喚醒后,進(jìn)入if (p == head && tryAcquire(arg))的判斷(即使p!=head也沒關(guān)系艰山,它會再進(jìn)入shouldParkAfterFailedAcquire()尋找一個離頭節(jié)點(diǎn)最近的一個等待位置湖雹。這里既然s已經(jīng)是等待隊列中最前邊的那個未放棄線程了,那么通過shouldParkAfterFailedAcquire()的調(diào)整曙搬,s也必然會跑到head的next結(jié)點(diǎn)劝枣,下一次循環(huán)判斷p==head就成立了),然后s把自己設(shè)置成head標(biāo)桿結(jié)點(diǎn)织鲸,表示自己已經(jīng)獲取到資源了舔腾,acquire()也返回了。

\color{blue}{2.6\ }共享鎖acquireShared/releaseShared方法解析

acquireShared(int)

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

tryAcquireShared()方法依然需要自定義同步器去實(shí)現(xiàn)搂擦,但是AQS已經(jīng)把其返回值的語義定義好了:負(fù)值代表獲取失斘瘸稀;0代表獲取成功瀑踢,但沒有剩余資源扳还;正數(shù)表示獲取成功才避,還有剩余資源,其他線程還可以去獲取氨距。所以這里acquireShared()的流程就是:

  1. tryAcquireShared()嘗試獲取資源桑逝,成功則直接返回;
  2. 失敗則通過doAcquireShared()進(jìn)入等待隊列俏让,直到獲取到資源為止才返回楞遏。

看下doAcquireShared源碼:

    private void doAcquireShared(int arg) {
        final Node node = addWaiter(Node.SHARED);//加入隊列尾部
        boolean failed = true;//是否成功標(biāo)志
        try {
            boolean interrupted = false;//等待過程中是否被中斷過的標(biāo)志
            for (;;) {
                final Node p = node.predecessor();//前驅(qū)
                if (p == head) {//如果到head的下一個,因?yàn)閔ead是拿到資源的線程首昔,此時node被喚醒寡喝,很可能是head用完資源來喚醒自己的
                    int r = tryAcquireShared(arg);//嘗試獲取資源
                    if (r >= 0) {//成功
                        setHeadAndPropagate(node, r);//將head指向自己,還有剩余資源可以再喚醒之后的線程
                        p.next = null; // help GC
                        if (interrupted)//如果等待過程中被打斷過勒奇,此時將中斷補(bǔ)上预鬓。
                            selfInterrupt();
                        failed = false;
                        return;
                    }
                }
                //判斷狀態(tài),尋找安全點(diǎn)赊颠,進(jìn)入waiting狀態(tài)格二,等著被unpark()或interrupt()
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

重點(diǎn)分析一下獲取鎖后的操作:setHeadAndPropagate:

    private void setHeadAndPropagate(Node node, int propagate) {
        Node h = head; // Record old head for check below
        setHead(node);
        /*
         * Try to signal next queued node if:
         *   Propagation was indicated by caller,
         *     or was recorded (as h.waitStatus either before
         *     or after setHead) by a previous operation
         *     (note: this uses sign-check of waitStatus because
         *      PROPAGATE status may transition to SIGNAL.)
         * and
         *   The next node is waiting in shared mode,
         *     or we don't know, because it appears null
         *
         * The conservatism in both of these checks may cause
         * unnecessary wake-ups, but only when there are multiple
         * racing acquires/releases, so most need signals now or soon
         * anyway.
         */
/**
     *    如果讀鎖(共享鎖)獲取成功,或頭部節(jié)點(diǎn)為空竣蹦,或頭節(jié)點(diǎn)取消蟋定,或剛獲取讀鎖的線程的下一個節(jié)點(diǎn)為空,
     *    或在節(jié)點(diǎn)的下個節(jié)點(diǎn)也在申請讀鎖草添,則在CLH隊列中傳播下去喚醒線程,怎么理解這個傳播呢扼仲,
     *    就是只要獲取成功到讀鎖远寸,那就要傳播到下一個節(jié)點(diǎn)(如果一下個節(jié)點(diǎn)繼續(xù)是讀鎖的申請,
     *    只要成功獲取屠凶,就再下一個節(jié)點(diǎn)驰后,直到隊列尾部或?yàn)閷戞i的申請,停止傳播)矗愧。具體請看doReleaseShared方法灶芝。
     *
     */
        if (propagate > 0 || h == null || h.waitStatus < 0 ||
            (h = head) == null || h.waitStatus < 0) {
            Node s = node.next;
            if (s == null || s.isShared())
                doReleaseShared();
        }
    }

其實(shí)跟acquire()的流程大同小異,只不過多了個自己拿到資源后唉韭,還會去喚醒后繼隊友的操作夜涕,這體現(xiàn)了共享鎖的定義。
releaseShared()

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

此方法的流程也比較簡單属愤,一句話:釋放掉資源后女器,喚醒后繼。跟獨(dú)占模式下的release()相似住诸,但有一點(diǎn)稍微需要注意:獨(dú)占模式下的tryRelease()在完全釋放掉資源(state=0)后驾胆,才會返回true去喚醒其他線程涣澡,這主要是基于可重入的考量;而共享模式下的releaseShared()則沒有這種要求丧诺,一是共享的實(shí)質(zhì)--多線程可并發(fā)執(zhí)行入桂;二是共享模式基本也不會重入吧(至少我還沒見過),所以自定義同步器可以根據(jù)需要決定返回值驳阎。

doReleaseShared()源碼:

private void doReleaseShared() {  
    for (;;) {  
        Node h = head;  
        if (h != null && h != tail) {   // 從隊列的頭部開始遍歷每一個節(jié)點(diǎn) 
            int ws = h.waitStatus;  
            // 如果節(jié)點(diǎn)狀態(tài)為 Node.SIGNAL,將狀態(tài)設(shè)置為0抗愁,設(shè)置成功,喚醒線程搞隐。
            // 為什么會設(shè)置不成功驹愚,可能改節(jié)點(diǎn)被取消;還有一種情況就是有多個線程在運(yùn)行該代碼段劣纲,這就是PROPAGATE的含義吧逢捺。
            // 如果一個節(jié)點(diǎn)的狀態(tài)設(shè)置為 Node.SIGNAL,則說明它有后繼節(jié)點(diǎn)癞季,并且處于阻塞狀態(tài)
            if (ws == Node.SIGNAL) { 
                if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))  
                    continue;            // loop to recheck cases  
                unparkSuccessor(h);  
            }
            // 如果狀態(tài)為0劫瞳,則設(shè)置為Node.PROPAGATE,設(shè)置為傳播绷柒,該值然后會在什么時候變化呢志于?
            // 在判斷該節(jié)點(diǎn)的下一個節(jié)點(diǎn)是否需要阻塞時,會判斷废睦,如果狀態(tài)不是Node.SIGNAL或取消狀態(tài)伺绽,
            // 為了保險起見,會將前置節(jié)點(diǎn)狀態(tài)設(shè)置為Node.SIGNAL嗜湃,然后再次判斷奈应,是否需要阻塞。
            else if (ws == 0 &&  
                    !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                continue;                // loop on failed CAS  
        }  
        /**
         * 如果處理過一次 unparkSuccessor 方法后购披,頭節(jié)點(diǎn)沒有發(fā)生變化杖挣,就退出該方法,那head在什么時候會改變呢刚陡?
         * 當(dāng)然在是搶占鎖成功的時候惩妇,head節(jié)點(diǎn)代表獲取鎖的節(jié)點(diǎn)。一旦獲取鎖成功筐乳,則又會進(jìn)入setHeadAndPropagate方法歌殃,
         * 當(dāng)然又會觸發(fā)doReleaseShared方法,傳播特性應(yīng)該就是表現(xiàn)在這里吧蝙云。再想一下挺份,同一時間,可以有多個多線程占有鎖贮懈,
         * 那在鎖釋放時匀泊,寫鎖的釋放比較簡單优训,就是從頭部節(jié)點(diǎn)下的第一個非取消節(jié)點(diǎn),喚醒線程即可各聘,
         * 為了在釋放讀鎖的上下文環(huán)境中獲取代表讀鎖的線程揣非,將信息存入在 readHolds ThreadLocal變量中。
         */
        if (h == head)                   // loop if head changed 
            break;  
    }  
}

參考文獻(xiàn)

  1. AbstractQueuedSynchronizer 源碼分析 (基于Java 8)
  2. AQS的ConditionObject源碼詳解
  3. 最不能忽略的AbstractQueuedSynchronizer類源碼分析(必看)
  4. Java多線程:AQS源碼分析
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末躲因,一起剝皮案震驚了整個濱河市早敬,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌大脉,老刑警劉巖搞监,帶你破解...
    沈念sama閱讀 221,406評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異镰矿,居然都是意外死亡琐驴,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,395評論 3 398
  • 文/潘曉璐 我一進(jìn)店門秤标,熙熙樓的掌柜王于貴愁眉苦臉地迎上來绝淡,“玉大人,你說我怎么就攤上這事苍姜±谓停” “怎么了?”我有些...
    開封第一講書人閱讀 167,815評論 0 360
  • 文/不壞的土叔 我叫張陵衙猪,是天一觀的道長馍乙。 經(jīng)常有香客問我,道長垫释,這世上最難降的妖魔是什么丝格? 我笑而不...
    開封第一講書人閱讀 59,537評論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮饶号,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘季蚂。我一直安慰自己茫船,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,536評論 6 397
  • 文/花漫 我一把揭開白布扭屁。 她就那樣靜靜地躺著算谈,像睡著了一般。 火紅的嫁衣襯著肌膚如雪料滥。 梳的紋絲不亂的頭發(fā)上然眼,一...
    開封第一講書人閱讀 52,184評論 1 308
  • 那天,我揣著相機(jī)與錄音葵腹,去河邊找鬼高每。 笑死屿岂,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的鲸匿。 我是一名探鬼主播爷怀,決...
    沈念sama閱讀 40,776評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼带欢!你這毒婦竟也來了运授?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,668評論 0 276
  • 序言:老撾萬榮一對情侶失蹤乔煞,失蹤者是張志新(化名)和其女友劉穎吁朦,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體渡贾,經(jīng)...
    沈念sama閱讀 46,212評論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡逗宜,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,299評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了剥啤。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片锦溪。...
    茶點(diǎn)故事閱讀 40,438評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖府怯,靈堂內(nèi)的尸體忽然破棺而出刻诊,到底是詐尸還是另有隱情,我是刑警寧澤牺丙,帶...
    沈念sama閱讀 36,128評論 5 349
  • 正文 年R本政府宣布则涯,位于F島的核電站,受9級特大地震影響冲簿,放射性物質(zhì)發(fā)生泄漏粟判。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,807評論 3 333
  • 文/蒙蒙 一峦剔、第九天 我趴在偏房一處隱蔽的房頂上張望档礁。 院中可真熱鬧,春花似錦吝沫、人聲如沸呻澜。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,279評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽羹幸。三九已至,卻和暖如春辫愉,著一層夾襖步出監(jiān)牢的瞬間栅受,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,395評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留屏镊,地道東北人依疼。 一個月前我還...
    沈念sama閱讀 48,827評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像闸衫,于是被迫代替她去往敵國和親涛贯。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,446評論 2 359

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