ReentrantLock源碼解析

前言

心血來潮想到要做一些源碼分析,想想從ReentrantLock類開始挺合適的闹获,就嘗試著先寫一篇吧学赛,廢話不多說進(jìn)入正題。

正文

說到ReentrantLock類首先肯定得看一下內(nèi)部類Sync斑匪,其定義如下(此處僅保留了一些重要部分呐籽,后面的源碼片段也會如此操作)

abstract static class Sync extends AbstractQueuedSynchronizer {

        abstract void lock();

        final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                if (compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }

        protected final boolean tryRelease(int releases) {
            int c = getState() - releases;
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;
            if (c == 0) {
                free = true;
                setExclusiveOwnerThread(null);
            }
            setState(c);
            return free;
        }

        protected final boolean isHeldExclusively() {
            return getExclusiveOwnerThread() == Thread.currentThread();
        }

        final ConditionObject newCondition() {
            return new ConditionObject();
        }


        final Thread getOwner() {
            return getState() == 0 ? null : getExclusiveOwnerThread();
        }

        final int getHoldCount() {
            return isHeldExclusively() ? getState() : 0;
        }

        final boolean isLocked() {
            return getState() != 0;
        }

    }

除了Sync還有兩個繼承了Sync的內(nèi)部類,分別是NonfairSync和FairSync秤标,分別對應(yīng)了非公平鎖和公平鎖

static final class NonfairSync extends Sync {

        final void lock() {
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);
        }

        protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
        }
    }
static final class FairSync extends Sync {

        final void lock() {
            acquire(1);
        }

        protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                if (!hasQueuedPredecessors() &&
                    compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0)
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }
    }

兩者的差別就是lock()方法和tryAcquire(int)方法的不同绝淡,其中l(wèi)ock()方法是Sync類定義的,而tryAcquire(int)方法是AbstractQueuedSynchronizer類定義的苍姜,這個類就是鎖類ReentrantLock和ReentrantReadWriteLock的核心,被稱之為AQS的類悬包,晚些再講解衙猪。這里順帶提一下,這種父類中定義一個抽象方法留待不同子類去具體實(shí)現(xiàn)的模式就是設(shè)計(jì)模式中的模板模式布近。繼續(xù)講源碼垫释,先來看一下ReentrantLock的lock()方法,可以看出實(shí)際的實(shí)現(xiàn)是由內(nèi)部類Sync的實(shí)現(xiàn)類來處理撑瞧,具體是FairSync還是NonfairSync的棵譬,就要看初始化時候的傳參了。

public void lock() {
        sync.lock();
    }

對比公平和非公平鎖的lock()實(shí)現(xiàn)预伺,acquire(1)這步是相同的订咸,不同的地方在于非公平鎖要進(jìn)行首次搶占,優(yōu)先執(zhí)行compareAndSetState(0, 1)酬诀。這個方法是屬于AQS的脏嚷,實(shí)現(xiàn)如下:

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

這里就碰到了非常經(jīng)典的CAS方法,簡單來說就是設(shè)置一個新值之前要判斷原值與期望值是否相同瞒御,相同才會設(shè)置新值父叙,并且會自旋重試,直到新值設(shè)置成功為止肴裙,CAS厲害在這一系列的動作都是原子的趾唱,避免了并發(fā)問題。同時unsafe對象是Unsafe類的實(shí)例蜻懦,該類就是jdk中專門用來調(diào)用這些封裝好的原子操作的甜癞。
此處CAS方法是第一次調(diào)用lock()方法時才會得到true,進(jìn)而執(zhí)行setExclusiveOwnerThread(Thread.currentThread())方法阻肩,其它情況下都是執(zhí)行acquire(1)方法去嘗試獲取鎖带欢。setExclusiveOwnerThread(Thread.currentThread())方法即是將當(dāng)前線程設(shè)置到exclusiveOwnerThread屬性上运授,即表示當(dāng)前線程獲得了鎖。

回過頭來看acquire(1)方法乔煞,該方法屬于AQS吁朦,具體實(shí)現(xiàn)如下

public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

可以看出首當(dāng)其沖就是調(diào)了tryAcquire方法,之前剛好提過渡贾,該方法的具體實(shí)現(xiàn)是交由Sync的子類的逗宜。這里非公平鎖也是很有趣,實(shí)現(xiàn)tryAcquire方法就是又回過去調(diào)用了Sync的nonfairTryAcquire方法

final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                if (compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }

將這個方法和公平鎖的tryAcquire對比一下發(fā)現(xiàn)空骚,相似度99%纺讲。唯一不同的地方只有公平鎖中多了!hasQueuedPredecessors()這個判斷條件

                if (!hasQueuedPredecessors() &&
                    compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }

說到hasQueuedPredecessors()這個方法,就不得不講AQS的設(shè)計(jì)結(jié)構(gòu)以及其中的CLH隊(duì)列囤屹,這里可以有一個簡單的概念就是AQS中維護(hù)了一個存放線程的鏈表熬甚,為了不擴(kuò)展的太遠(yuǎn),所以先來分析非公平鎖的流程肋坚。

首先根據(jù)getState()獲得當(dāng)前線程的狀態(tài)c乡括,可以看到c為0時可以獲得鎖,這與之前提到的非公平鎖的第一步操作又是相似度極高智厌。當(dāng)c不為0時诲泌,執(zhí)行current == getExclusiveOwnerThread()判斷,為true說明當(dāng)前線程是占用著該鎖的線程铣鹏,對c進(jìn)行累加操作直到c溢出時拋異常敷扫。此時跳回AQS的acquire方法,當(dāng)tryAcquire(1)的結(jié)果為false就有必要進(jìn)行acquireQueued(addWaiter(Node.EXCLUSIVE), arg)的判斷了诚卸,接著就來看看這個方法葵第。

final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                final Node p = node.predecessor();
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

這里入?yún)⑿枰粋€node,而方法調(diào)用時填的是addWaiter(Node.EXCLUSIVE)惨险,這里的EXCLUSIVE指的是獨(dú)占的意思羹幸,意味著這是一個獨(dú)占鎖的操作,只有一個線程可以占有鎖辫愉,與之相對的還有一種是SHARED栅受,即共享鎖,ReentrantReadWriteLock就是一種共享鎖恭朗,這個以后有機(jī)會再展開屏镊。

private Node addWaiter(Node mode) {
        Node node = new Node(Thread.currentThread(), mode);
        // Try the fast path of enq; backup to full enq on failure
        Node pred = tail;
        if (pred != null) {
            node.prev = pred;
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
        enq(node);
        return node;
    } 

接著看addWaiter()方法的實(shí)現(xiàn),這個方法概括起來就是將新生成的Node.EXCLUSIVE節(jié)點(diǎn)放到原來的隊(duì)尾節(jié)點(diǎn)的后面變成新的隊(duì)尾節(jié)點(diǎn)痰腮。這里用到了compareAndSetTail(pred, node)這個方法而芥,看一下實(shí)現(xiàn)

private final boolean compareAndSetTail(Node expect, Node update) {
        return unsafe.compareAndSwapObject(this, tailOffset, expect, update);
    }

又看到了熟悉的unsafe的CAS操作,很明顯就是為了防止并發(fā)的情況膀值。

繼續(xù)拉回來看acquireQueued()方法棍丐,這里的主體是一個for循環(huán)误辑,還是一個死循環(huán),唯一跳出循環(huán)的可能是return interrupted這里歌逢,一看這條件我真的是倒吸一口涼氣巾钉,里面遞歸調(diào)用了tryAcquire()做為判斷條件,說實(shí)話這種操作不是對全局有很強(qiáng)的把控的話真的不敢隨便亂用秘案∨椴裕回過頭去看一眼tryAcquire()方法為true的條件是什么,當(dāng)然就是當(dāng)前線程必須要獲得鎖阱高。而p == head就是判斷p這個節(jié)點(diǎn)是不是頭節(jié)點(diǎn)赚导,那么這個p又是個,來看p = node.predecessor()的代碼

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

一看這方法其實(shí)就是返回上一個節(jié)點(diǎn)赤惊,p == head判斷的就是上一個節(jié)點(diǎn)是否是頭結(jié)點(diǎn)吼旧。連起來看就是當(dāng)上一節(jié)點(diǎn)是頭結(jié)點(diǎn)并且能獲得鎖的情況下結(jié)束循環(huán)返回false,如下的整個acquire()方法就結(jié)束了未舟,整個lock()過程也就結(jié)束了黍少。

public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

跳回for循環(huán)中,如果不能觸發(fā)return又是一直在做什么呢

if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;

首先看一下shouldParkAfterFailedAcquire(p, node)在干什么

private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        int ws = pred.waitStatus;
        if (ws == Node.SIGNAL)
            return true;
        if (ws > 0) {
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node;
        } else {
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
    }

可以看到這個方法返回值是boolean類型的处面,根據(jù)描述當(dāng)線程需要阻塞時返回true。其中的判斷條件只有一個菩掏,就是pred.waitStatus魂角,即前一個節(jié)點(diǎn)的waitStatus狀態(tài)。

        static final int CANCELLED =  1;
        static final int SIGNAL    = -1;
        static final int CONDITION = -2;
        static final int PROPAGATE = -3;

         /*
         * 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).
         */
        volatile int waitStatus;

根據(jù)常量的定義和屬性的描述可以看出智绸,waitStatus的取值有0野揪,1,-1瞧栗,-2斯稳,-3這幾種情況,正常情況下初始值為0,并且這是一個volatile類型的屬性迹恐,為了保證可見性更新這個值需要CAS操作挣惰。然后看判斷條件,這里分成了三種情況殴边,第一種狀態(tài)為SIGNAL憎茂,即等待狀態(tài),線程需阻塞锤岸;第二種waitStatus>0竖幔,其實(shí)也只有CANCELLED這一種情況,這時做的事情是遞歸的將當(dāng)前節(jié)點(diǎn)連接到前一個節(jié)點(diǎn)的上一個節(jié)點(diǎn)是偷,也就是從這條鏈上去除掉所有CANCELLED狀態(tài)的節(jié)點(diǎn)拳氢;第三種也就是剩下的狀態(tài)募逞,調(diào)用compareAndSetWaitStatus方法,來看一下其實(shí)現(xiàn)

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

又見到了unsafe的CAS操作馋评,那么顯而易見第三種情況下的操作就是將上一個節(jié)點(diǎn)狀態(tài)置為SIGNAL放接。

這里可以總結(jié)一下shouldParkAfterFailedAcquire的作用了,那就是根據(jù)上一個節(jié)點(diǎn)的情況來做相應(yīng)的操作栗恩。接著就要看parkAndCheckInterrupt()方法了透乾。

private final boolean parkAndCheckInterrupt() {
        LockSupport.park(this);
        return Thread.interrupted();
    }

關(guān)于這個LockSupport類,這里不準(zhǔn)備再深入了磕秤,里面又是調(diào)用到了unsafe進(jìn)行一系列較為底層的操作乳乌,見但介紹一下LockSupport這個類,它是用來創(chuàng)建鎖和其他同步類的基本線程阻塞原語市咆。LockSupport 提供park()和unpark()方法實(shí)現(xiàn)阻塞線程和解除線程阻塞汉操,暫時了解到這一層就夠了。
看上去流程基本是講完了蒙兰,該總結(jié)一下非公平鎖和公平鎖的差異了磷瘤,等等,好像之前hasQueuedPredecessors()方法還晾在一邊沒講搜变。

public final boolean hasQueuedPredecessors() {
        Node t = tail; // Read fields in reverse initialization order
        Node h = head;
        Node s;
        return h != t &&
            ((s = h.next) == null || s.thread != Thread.currentThread());
    }

總結(jié)一下采缚,這個方法做的事情是判斷等待隊(duì)列中有沒有節(jié)點(diǎn)。都到這里了挠他,是時候引出沒講的AQS了扳抽。

public abstract class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer{
    
    private transient volatile Node head;
    private transient volatile Node tail;
    private volatile int state;
    
     static final class Node {
        
        static final Node SHARED = new Node();
        static final Node EXCLUSIVE = null;
        static final int CANCELLED =  1;
        static final int SIGNAL    = -1;
        static final int CONDITION = -2;
        static final int PROPAGATE = -3;
        
        volatile int waitStatus;
        volatile Node prev;
        volatile Node next;
        volatile Thread thread;
        Node nextWaiter;

     }
}

public abstract class AbstractOwnableSynchronizer{
    
    private transient Thread exclusiveOwnerThread;
}

我把所有的方法和細(xì)枝末節(jié)的東西都刪了,呈現(xiàn)出來類結(jié)構(gòu)是這樣的殖侵,AQS連同它繼承的AOS類一共就只有這些屬性贸呢,重點(diǎn)是exclusiveOwnerThread屬性,這個屬性就是持有鎖的線程拢军,也就是真正的boss楞陷。整個鎖其實(shí)本質(zhì)就是圍繞兩個點(diǎn),線程和隊(duì)列茉唉,內(nèi)部類NODE有一個注釋寫的非常好固蛾,有必要放一下。

     * <p>To enqueue into a CLH lock, you atomically splice it in as new
     * tail. To dequeue, you just set the head field.
     * <pre>
     *      +------+  prev +-----+       +-----+
     * head |      | <---- |     | <---- |     |  tail
     *      +------+       +-----+       +-----+
     * </pre>

這個注釋真的是高赌渣,一下子就展示出了CLH隊(duì)列的含義魏铅,這個CLH隊(duì)列中每一個節(jié)點(diǎn)就是一個NODE對象,NODE對象上記錄了對應(yīng)的排隊(duì)獲取鎖的線程坚芜,狀態(tài)用waitStatus表示览芳,規(guī)則是tail入隊(duì)head出隊(duì),當(dāng)然這里的入隊(duì)和出隊(duì)操作都是基于CAS封裝的原子操作鸿竖。分析到這里我們就可以總結(jié)出整個鎖的核心原理了沧竟,==那就是通過CLH隊(duì)列將多線程爭搶鎖的并行過程變成隊(duì)列中排隊(duì)獲取鎖的串行過程==铸敏。

最后用一張網(wǎng)上看到的畫得非常好的流程圖來區(qū)別一下公平鎖和非公平鎖的區(qū)別,區(qū)別有兩點(diǎn):==一悟泵、非公平鎖多了首次搶占杈笔;二、公平鎖在基于CLH隊(duì)列獲取鎖的過程中還要判斷隊(duì)列中是否有等待節(jié)點(diǎn)==糕非。這兩點(diǎn)也很好的展示了兩種鎖模式的特性蒙具。

NonfairSync
lock —> compareAndSetState
                | —> setExclusiveOwnerThread
      —> accquire
             | —> tryAcquire
                           |—>nonfairTryAcquire
                |—> acquireQueued

FairSync
lock —> acquire
               | —> tryAcquire
                           |—>!hasQueuePredecessors
                           |—>compareAndSetState
                           |—>setExclusiveOwnerThread
               |—> acquireQueued

總結(jié)

本文主要是對ReentrantLock中重點(diǎn)源碼的一個梳理和總結(jié),講解了獲取鎖的核心原理朽肥,并且對比了公平鎖和非公平鎖的區(qū)別禁筏。后續(xù)考慮逐步把concurrent包里的幾個常用的重要類都講一下,但是寫一篇分析真的太耗時間了衡招,我自己也不確定能寫幾篇篱昔,走著看吧。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末始腾,一起剝皮案震驚了整個濱河市州刽,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌浪箭,老刑警劉巖穗椅,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異奶栖,居然都是意外死亡房待,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進(jìn)店門驼抹,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人拜鹤,你說我怎么就攤上這事框冀。” “怎么了敏簿?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵明也,是天一觀的道長。 經(jīng)常有香客問我惯裕,道長温数,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任蜻势,我火速辦了婚禮撑刺,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘握玛。我一直安慰自己够傍,他們只是感情好甫菠,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著冕屯,像睡著了一般寂诱。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上安聘,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天痰洒,我揣著相機(jī)與錄音,去河邊找鬼浴韭。 笑死丘喻,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的囱桨。 我是一名探鬼主播仓犬,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼舍肠!你這毒婦竟也來了搀继?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤翠语,失蹤者是張志新(化名)和其女友劉穎叽躯,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體肌括,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡点骑,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了谍夭。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片黑滴。...
    茶點(diǎn)故事閱讀 39,690評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖紧索,靈堂內(nèi)的尸體忽然破棺而出袁辈,到底是詐尸還是另有隱情,我是刑警寧澤珠漂,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布晚缩,位于F島的核電站,受9級特大地震影響媳危,放射性物質(zhì)發(fā)生泄漏荞彼。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一待笑、第九天 我趴在偏房一處隱蔽的房頂上張望鸣皂。 院中可真熱鬧,春花似錦、人聲如沸签夭。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽第租。三九已至措拇,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間慎宾,已是汗流浹背丐吓。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留趟据,地道東北人券犁。 一個月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像汹碱,于是被迫代替她去往敵國和親粘衬。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,577評論 2 353

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