ReentrantReadWriteLock源碼解析

簡(jiǎn)介

ReentrantReadWriteLock是juc包下的一個(gè)讀寫鎖工具類餐济,該類內(nèi)部維護(hù)著一個(gè)讀鎖和一個(gè)寫鎖暴拄,通過讀鎖與寫鎖的分離使得在讀多寫少的壞境下性能有了很好的提升苦掘,可以減少線程的總等待時(shí)間畸写。
注意點(diǎn):

  • 讀讀不阻塞贴铜,同一時(shí)間可以允許多個(gè)線程進(jìn)行讀鎖的獲取。
  • 讀寫叹括、寫讀阻塞算墨,也就是讀鎖被線程獲取的時(shí)候,其他線程不能獲取寫鎖汁雷,寫鎖被線程獲取的時(shí)候其他線程不能進(jìn)行讀鎖的獲取净嘀。

主要特性:
1.公平性,ReentrantReadWriteLock構(gòu)造函數(shù)支持公平以及非公平模式侠讯。
2.可重入性挖藏,ReentrantReadWriteLock讀鎖與寫鎖都支持鎖的重入功能。
3.鎖降級(jí)厢漩,ReentrantReadWriteLock在獲取寫鎖的同時(shí)可以重入讀鎖熬苍,然后釋放鎖的時(shí)候先釋放寫鎖以達(dá)到鎖的降級(jí)。(注意ReentrantReadWriteLock只能進(jìn)行鎖降級(jí)不能進(jìn)行鎖升級(jí),鎖升級(jí)的時(shí)候會(huì)無限等待進(jìn)入死鎖)

ReentrantReadWriteLock內(nèi)部結(jié)構(gòu)

/*內(nèi)部讀鎖*/
private final ReentrantReadWriteLock.ReadLock readerLock;
/*內(nèi)部寫鎖*/
private final ReentrantReadWriteLock.WriteLock writerLock;
/*無參構(gòu)造器 默認(rèn)是給公平模式*/
public ReentrantReadWriteLock();
/*公平模式構(gòu)造器*/
public ReentrantReadWriteLock(boolean fair);
/*獲取寫鎖方法*/
public ReentrantReadWriteLock.WriteLock writeLock();
/*獲取讀鎖方法*/
public ReentrantReadWriteLock.ReadLock  readLock();
/*內(nèi)部類同步器  基于aqs實(shí)現(xiàn)功能*/
abstract static class Sync extends AbstractQueuedSynchronizer{}
/*非公平sync*/
static final class NonfairSync extends Sync{}
/*公平sync*/
static final class FairSync extends Sync{}
/*讀鎖類*/
public static class ReadLock implements Lock, java.io.Serializable{}
/*寫鎖類*/
public static class WriteLock implements Lock, java.io.Serializable{}
類繼承結(jié)構(gòu)圖

ReentrantReadWriteLock內(nèi)部主要也是基于sync去實(shí)現(xiàn)的柴底,無論是讀鎖還是寫鎖都是基于sync去操作的,內(nèi)部都是操作的AbstractQueuedSynchronizer的state變量粱胜,那么如果用一個(gè)變量實(shí)現(xiàn)兩個(gè)鎖的狀態(tài)呢柄驻,這個(gè)地方就需要牽涉到位操作了,ReentrantReadWriteLock將變量32位的state拆分為高16位和低16位焙压,高位給讀鎖使用鸿脓,低位給寫鎖使用。具體操作代碼如下:

static final int SHARED_SHIFT   = 16;
static final int SHARED_UNIT    = (1 << SHARED_SHIFT);
static final int MAX_COUNT      = (1 << SHARED_SHIFT) - 1;
static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;
static int sharedCount(int c)    { return c >>> SHARED_SHIFT; }
static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }

ReadLock與WriteLock

ReadLock與WriteLock都是其ReentrantReadWriteLock類的內(nèi)部類涯曲,這兩個(gè)鎖都實(shí)現(xiàn)于lock接口野哭,也都重寫了相關(guān)的鎖獲取以及釋放方法,那么接下來我們就兩個(gè)鎖的鎖獲取以及釋放進(jìn)行分析幻件。

WriteLock鎖獲取以及鎖釋放原理

寫鎖的獲取

        public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
        }
        protected final boolean tryAcquire(int acquires) {
            Thread current = Thread.currentThread();
            int c = getState();
            int w = exclusiveCount(c);
            if (c != 0) {
                // (Note: if c != 0 and w == 0 then shared count != 0)
                if (w == 0 || current != getExclusiveOwnerThread())
                    return false;
                if (w + exclusiveCount(acquires) > MAX_COUNT)
                    throw new Error("Maximum lock count exceeded");
                // Reentrant acquire
                setState(c + acquires);
                return true;
            }
            if (writerShouldBlock() ||
                !compareAndSetState(c, c + acquires))
                return false;
            setExclusiveOwnerThread(current);
            return true;
        }

tryAcquire是寫鎖的獲取鎖的主要方法拨黔。首先獲取到state變量值c,然后對(duì)變量值state進(jìn)行c&((1 << 16) - 1)操作來獲取到當(dāng)前寫鎖的值绰沥,然后對(duì)判斷state是否等于0篱蝇。

  • 如果等于0表示當(dāng)前沒有線程獲取到鎖,先查看當(dāng)前隊(duì)列是否有優(yōu)先的等待隊(duì)列(公平模式才會(huì)檢查徽曲,非公平模式直接返回false)零截,如果有直接返回true,沒有則對(duì)state值直接進(jìn)行cas替換操作秃臣,將c+acquires賦值給c涧衙,如果成功則設(shè)置exclusiveOwnerThread為當(dāng)前線程,失敗直接返回奥此。
  • 如果不等于0(表示已經(jīng)有線程獲取到了鎖)弧哎,首先判斷w是否為0(也就是寫鎖是否被獲取)得院,如果寫鎖沒有被其他線程獲取或者獲取寫鎖的線程不是當(dāng)前線程則直接返回false 傻铣。其次判斷寫鎖的值是不是超過范圍,因?yàn)閷戞i與讀鎖的值被拆分為16位的數(shù)字祥绞,則該數(shù)字最大值為65535非洲,如果超過范圍直接拋出異常。上述條件都通過直接對(duì)c進(jìn)行運(yùn)算并賦值給c蜕径,這里大家可以關(guān)注到賦值并未做cas原子操作两踏,是因?yàn)樵摼€程已經(jīng)獲取到寫鎖了,變量操作是線程安全的所以不需要進(jìn)行cas操作兜喻。

寫鎖的釋放

    public final boolean release(int arg) {
        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }
    protected final boolean tryRelease(int releases) {
            if (!isHeldExclusively())
                throw new IllegalMonitorStateException();
            int nextc = getState() - releases;
            boolean free = exclusiveCount(nextc) == 0;
            if (free)
                setExclusiveOwnerThread(null);
            setState(nextc);
            return free;
        }

寫鎖的釋放主要邏輯在tryRelease方法中梦染,release只是調(diào)用了tryRelease方法,如果成功對(duì)其后繼節(jié)點(diǎn)進(jìn)行喚醒,如果后繼節(jié)點(diǎn)存在的話帕识。我們主要看下tryRelease方法邏輯泛粹,首先判斷占有寫鎖的線程是否是當(dāng)前線程,如果不是直接拋出異常IllegalMonitorStateException肮疗,如果是當(dāng)前線程則對(duì)state進(jìn)行計(jì)算晶姊,c=state-releases,如果c的低十六位數(shù)值為零伪货,則說明寫鎖已經(jīng)被釋放们衙,然后將ownerThread置為null,將c賦值給state返回c寫鎖的數(shù)值碱呼。這里在對(duì)state賦值的時(shí)候也沒有涉及cas操作蒙挑,原理是一樣的因?yàn)閷戞i是獨(dú)占鎖,然后獲取占有寫鎖的線程進(jìn)行變量替換都是線程安全的愚臀。

ReadLock鎖獲取以及鎖釋放原理

讀鎖的獲取

        public final void acquireShared(int arg) {
        if (tryAcquireShared(arg) < 0)
            doAcquireShared(arg);
        }
        /*嘗試獲取共享鎖*/
        protected final int tryAcquireShared(int unused) {
            Thread current = Thread.currentThread();
            int c = getState();
            if (exclusiveCount(c) != 0 &&
                getExclusiveOwnerThread() != current)//1
                return -1;
            int r = sharedCount(c);//2
            if (!readerShouldBlock() &&
                r < MAX_COUNT &&
                compareAndSetState(c, c + SHARED_UNIT)) {//3
                if (r == 0) {//4
                    firstReader = current;
                    firstReaderHoldCount = 1;
                } else if (firstReader == current) {//4
                    firstReaderHoldCount++;
                } else {//4
                    HoldCounter rh = cachedHoldCounter;
                    if (rh == null || rh.tid != getThreadId(current))
                        cachedHoldCounter = rh = readHolds.get();
                    else if (rh.count == 0)
                        readHolds.set(rh);
                    rh.count++;
                }
                return 1;
            }
            return fullTryAcquireShared(current);
        }
        
        /*以不間斷模式進(jìn)行獲取共享鎖*/
        private void doAcquireShared(int arg) {
        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);
        }
    }

讀鎖的獲取稍微復(fù)雜一些忆蚀,先看下tryAcquireShared方法,tryAcquireShared是嘗試獲取鎖懊悯,如果失敗直接進(jìn)入到doAcquireShared方法中蜓谋。
tryAcquireShared方法:
1.首先判斷寫鎖是否被占有,如果被占有查看占有線程是否是當(dāng)前線程炭分,如果不是直接返回false桃焕,如果是則進(jìn)入讀鎖的獲取,這段邏輯也表示寫鎖可重入讀鎖捧毛,如果后續(xù)寫鎖先釋放那么也就達(dá)到了鎖的降級(jí)功能观堂。
2.獲取state高十六位數(shù)值(也就是當(dāng)前獲取讀鎖的數(shù)量),r = state>>>16呀忧。
3.查看當(dāng)前隊(duì)列aqs中是否有優(yōu)先的等待節(jié)點(diǎn)师痕,如果有直接跳過進(jìn)入fullTryAcquireShared,沒有進(jìn)行讀鎖數(shù)量判斷而账,不能大于等于最大數(shù)值65535胰坟,如果大于進(jìn)入fullTryAcquireShared,沒有進(jìn)行讀鎖數(shù)量+1并cas賦值泞辐,成功進(jìn)行下一步笔横,否則進(jìn)入fullTryAcquireShared方法。
4.進(jìn)入這一步表示讀鎖已經(jīng)成功獲取咐吼,接下來主要是對(duì)單個(gè)線程的讀鎖重入次數(shù)進(jìn)行更新吹缔,先查看r值,如果r值為零表示當(dāng)前沒有線程獲取讀鎖锯茄,則設(shè)置為firstReader為當(dāng)前線程厢塘,并且將firstReaderHoldCount值設(shè)置為1茶没,如果r不等于0(表示讀鎖已被其他線程獲取)晚碾,判斷firstReader==current抓半,如果相等進(jìn)行firstReaderHoldCount++操作,如果不相等從ThreadLocalHoldCounter對(duì)象中獲取HoldCounter迄薄,如果不存在創(chuàng)建HoldCounter放入ThreadLocalMap中琅关,并對(duì)HoldCounter的value做++操作。

注意:tryAcquireShared方法結(jié)尾處會(huì)調(diào)用fullTryAcquireShared讥蔽,fullTryAcquireShared這個(gè)方法其實(shí)也就是tryAcquireShared方法的自選模式盐类,里面的邏輯沒有太大的改變讯沈。

HoldCounter 用來儲(chǔ)存單個(gè)線程獲取讀鎖的重入數(shù)
cachedHoldCounter儲(chǔ)存最后一個(gè)獲取讀鎖的HoldCounter
firstReader表示第一個(gè)獲取讀鎖的線程脐供,只有一個(gè)線程獲取讀鎖的時(shí)候提高性能壹将,避免通過ThreadLocal去儲(chǔ)存择示。
firstReaderHoldCount表示第一個(gè)獲取讀鎖的線程的重入數(shù) 查辩,只有一個(gè)線程獲取讀鎖的時(shí)候提高性能并村,避免通過ThreadLocal去儲(chǔ)存嘉蕾。

doAcquireShared方法:
1.創(chuàng)建shared類型node排隊(duì)節(jié)點(diǎn)荚醒。然后進(jìn)入自旋代碼塊芋类。
2.獲取node的前置節(jié)點(diǎn),如果前置節(jié)點(diǎn)等于head界阁,進(jìn)入tryAcquireShared方法侯繁,如果tryAcquireShared執(zhí)行成功,則執(zhí)行進(jìn)行喚醒后繼節(jié)點(diǎn)泡躯,執(zhí)行完畢后返回贮竟。
3.判斷是否需要阻塞當(dāng)前線程,如果需要?jiǎng)t直接阻塞较剃。

讀鎖的釋放

        protected final boolean tryReleaseShared(int unused) {
            Thread current = Thread.currentThread();
            if (firstReader == current) {
                if (firstReaderHoldCount == 1)
                    firstReader = null;
                else
                    firstReaderHoldCount--;
            } else {
                HoldCounter rh = cachedHoldCounter;
                if (rh == null || rh.tid != getThreadId(current))
                    rh = readHolds.get();
                int count = rh.count;
                if (count <= 1) {
                    readHolds.remove();
                    if (count <= 0)
                        throw unmatchedUnlockException();
                }
                --rh.count;
            }
            for (;;) {
                int c = getState();
                int nextc = c - SHARED_UNIT;
                if (compareAndSetState(c, nextc))
                    return nextc == 0;
            }
        }

讀鎖的釋放主要邏輯在tryReleaseShared方法中咕别,tryReleaseShared釋放讀鎖的邏輯也相對(duì)較簡(jiǎn)單。
1.獲取當(dāng)前線程對(duì)象写穴,判斷是否等于firstReader惰拱,如果等于則直接對(duì)firstReader以及firstReaderHoldCount進(jìn)行操作,如果不等于firstReader進(jìn)入下一步啊送。
2.從ThreadLocalMap中獲取HoldCounter對(duì)象偿短,判斷HoldCounter對(duì)象的count是否小于等于1,如果小于1則直接移除ThreadLocalMap中的HoldCounter删掀,然后對(duì)count進(jìn)行自減操作翔冀。
3.最后對(duì)state變量進(jìn)行操作,對(duì)aqs的state高十六位(讀鎖的數(shù)值)進(jìn)行減一操作披泪,然后進(jìn)行cas將新的讀鎖數(shù)值賦值給state變量纤子,然后返回結(jié)果。

總結(jié)

本文就ReentrantReadWriteLock的讀鎖與寫鎖的獲取以及釋放的源碼進(jìn)行了簡(jiǎn)單的分析,讀寫鎖最核心的原理是將state值進(jìn)行高16位以及低16位進(jìn)行切割管理控硼,以達(dá)到一個(gè)變量可同時(shí)實(shí)現(xiàn)讀寫鎖的操作泽论,其實(shí)ReentrantReadWriteLock內(nèi)部還有很多其他的方法,感興趣的小伙伴可以仔細(xì)閱讀下卡乾,ReentrantReadWriteLock適合在讀多寫少的場(chǎng)景使用翼悴,在寫多讀少的場(chǎng)景下其實(shí)效率并不比ReentrantLock的效率高,并且需要特別注意的是讀寫鎖可以進(jìn)行鎖降級(jí)幔妨,但是一定注意代碼不要出現(xiàn)鎖升級(jí)鹦赎,鎖升級(jí)會(huì)導(dǎo)致死鎖。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末误堡,一起剝皮案震驚了整個(gè)濱河市古话,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌锁施,老刑警劉巖陪踩,帶你破解...
    沈念sama閱讀 211,265評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異悉抵,居然都是意外死亡肩狂,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,078評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門姥饰,熙熙樓的掌柜王于貴愁眉苦臉地迎上來傻谁,“玉大人,你說我怎么就攤上這事媳否≌っ” “怎么了?”我有些...
    開封第一講書人閱讀 156,852評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵篱竭,是天一觀的道長力图。 經(jīng)常有香客問我,道長掺逼,這世上最難降的妖魔是什么吃媒? 我笑而不...
    開封第一講書人閱讀 56,408評(píng)論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮吕喘,結(jié)果婚禮上赘那,老公的妹妹穿的比我還像新娘。我一直安慰自己氯质,他們只是感情好募舟,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,445評(píng)論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著闻察,像睡著了一般拱礁。 火紅的嫁衣襯著肌膚如雪琢锋。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,772評(píng)論 1 290
  • 那天呢灶,我揣著相機(jī)與錄音吴超,去河邊找鬼。 笑死鸯乃,一個(gè)胖子當(dāng)著我的面吹牛鲸阻,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播缨睡,決...
    沈念sama閱讀 38,921評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼鸟悴,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了奖年?” 一聲冷哼從身側(cè)響起遣臼,我...
    開封第一講書人閱讀 37,688評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎拾并,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體鹏浅,經(jīng)...
    沈念sama閱讀 44,130評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡嗅义,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,467評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了隐砸。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片之碗。...
    茶點(diǎn)故事閱讀 38,617評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖季希,靈堂內(nèi)的尸體忽然破棺而出褪那,到底是詐尸還是另有隱情,我是刑警寧澤式塌,帶...
    沈念sama閱讀 34,276評(píng)論 4 329
  • 正文 年R本政府宣布博敬,位于F島的核電站,受9級(jí)特大地震影響峰尝,放射性物質(zhì)發(fā)生泄漏偏窝。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,882評(píng)論 3 312
  • 文/蒙蒙 一武学、第九天 我趴在偏房一處隱蔽的房頂上張望祭往。 院中可真熱鬧,春花似錦火窒、人聲如沸硼补。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,740評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽已骇。三九已至离钝,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間疾捍,已是汗流浹背奈辰。 一陣腳步聲響...
    開封第一講書人閱讀 31,967評(píng)論 1 265
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留乱豆,地道東北人奖恰。 一個(gè)月前我還...
    沈念sama閱讀 46,315評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像宛裕,于是被迫代替她去往敵國和親瑟啃。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,486評(píng)論 2 348