ReentrantReadWriteLock讀寫鎖詳解

一央渣、讀寫鎖簡(jiǎn)介

現(xiàn)實(shí)中有這樣一種場(chǎng)景:對(duì)共享資源有讀和寫的操作计盒,且寫操作沒有讀操作那么頻繁。在沒有寫操作的時(shí)候芽丹,多個(gè)線程同時(shí)讀一個(gè)資源沒有任何問題北启,所以應(yīng)該允許多個(gè)線程同時(shí)讀取共享資源;但是如果一個(gè)線程想去寫這些共享資源拔第,就不應(yīng)該允許其他線程對(duì)該資源進(jìn)行讀和寫的操作了咕村。
針對(duì)這種場(chǎng)景,JAVA的并發(fā)包提供了讀寫鎖ReentrantReadWriteLock蚊俺,它表示兩個(gè)鎖懈涛,一個(gè)是讀操作相關(guān)的鎖,稱為共享鎖泳猬;一個(gè)是寫相關(guān)的鎖批钠,稱為排他鎖宇植,描述如下:
線程進(jìn)入讀鎖的前提條件:
沒有其他線程的寫鎖,
沒有寫請(qǐng)求或者有寫請(qǐng)求埋心,但調(diào)用線程和持有鎖的線程是同一個(gè)指郁。
線程進(jìn)入寫鎖的前提條件:
沒有其他線程的讀鎖
沒有其他線程的寫鎖
而讀寫鎖有以下三個(gè)重要的特性:
(1)公平選擇性:支持非公平(默認(rèn))和公平的鎖獲取方式,吞吐量還是非公平優(yōu)于公平拷呆。
(2)重進(jìn)入:讀鎖和寫鎖都支持線程重進(jìn)入坡氯。
(3)鎖降級(jí):遵循獲取寫鎖、獲取讀鎖再釋放寫鎖的次序洋腮,寫鎖能夠降級(jí)成為讀鎖箫柳。
二、源碼解讀
我們先來看下 ReentrantReadWriteLock 類的整體結(jié)構(gòu):

    /** 讀鎖 */
    private final ReentrantReadWriteLock.ReadLock readerLock;
    /** 寫鎖 */
    private final ReentrantReadWriteLock.WriteLock writerLock;
    final Sync sync;    
    /** 使用默認(rèn)(非公平)的排序?qū)傩詣?chuàng)建一個(gè)新的 ReentrantReadWriteLock */
    public ReentrantReadWriteLock() {
        this(false);
    }
    /** 使用給定的公平策略創(chuàng)建一個(gè)新的 ReentrantReadWriteLock */
    public ReentrantReadWriteLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
        readerLock = new ReadLock(this);
        writerLock = new WriteLock(this);
    }
    /** 返回用于寫入操作的鎖 */
    public ReentrantReadWriteLock.WriteLock writeLock() { return writerLock; }    
    /** 返回用于讀取操作的鎖 */
    public ReentrantReadWriteLock.ReadLock  readLock()  { return readerLock; }
    abstract static class Sync extends AbstractQueuedSynchronizer {}
    static final class NonfairSync extends 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 {}
}

1啥供、類的繼承關(guān)系

public class ReentrantReadWriteLock
        implements ReadWriteLock, java.io.Serializable {}

說明:可以看到悯恍,ReentrantReadWriteLock實(shí)現(xiàn)了ReadWriteLock接口,ReadWriteLock接口定義了獲取讀鎖和寫鎖的規(guī)范伙狐,具體需要實(shí)現(xiàn)類去實(shí)現(xiàn)涮毫;同時(shí)其還實(shí)現(xiàn)了Serializable接口,表示可以進(jìn)行序列化贷屎,在源代碼中可以看到ReentrantReadWriteLock實(shí)現(xiàn)了自己的序列化邏輯罢防。

2、類的內(nèi)部類

ReentrantReadWriteLock有五個(gè)內(nèi)部類唉侄,五個(gè)內(nèi)部類之間也是相互關(guān)聯(lián)的咒吐。內(nèi)部類的關(guān)系如下圖所示。

說明:如上圖所示属划,Sync繼承自AQS恬叹、NonfairSync繼承自Sync類、FairSync繼承自Sync類(通過構(gòu)造函數(shù)傳入的布爾值決定要構(gòu)造哪一種Sync實(shí)例)同眯;ReadLock實(shí)現(xiàn)了Lock接口绽昼、WriteLock也實(shí)現(xiàn)了Lock接口。
Sync類:

(1)類的繼承關(guān)系

abstract static class Sync extends AbstractQueuedSynchronizer {}
說明:Sync抽象類繼承自AQS抽象類须蜗,Sync類提供了對(duì)ReentrantReadWriteLock的支持硅确。

(2)類的內(nèi)部類

Sync類內(nèi)部存在兩個(gè)內(nèi)部類,分別為HoldCounter和ThreadLocalHoldCounter明肮,其中HoldCounter主要與讀鎖配套使用菱农,其中,HoldCounter源碼如下晤愧。

// 計(jì)數(shù)器
static final class HoldCounter {
    // 計(jì)數(shù)
    int count = 0;
    // Use id, not reference, to avoid garbage retention
    // 獲取當(dāng)前線程的TID屬性的值
    final long tid = getThreadId(Thread.currentThread());
}

說明:HoldCounter主要有兩個(gè)屬性大莫,count和tid蛉腌,其中count表示某個(gè)讀線程重入的次數(shù)官份,tid表示該線程的tid字段的值只厘,該字段可以用來唯一標(biāo)識(shí)一個(gè)線程。ThreadLocalHoldCounter的源碼如下

// 本地線程計(jì)數(shù)器
static final class ThreadLocalHoldCounter
    extends ThreadLocal<HoldCounter> {
    // 重寫初始化方法舅巷,在沒有進(jìn)行set的情況下羔味,獲取的都是該HoldCounter值
    public HoldCounter initialValue() {
        return new HoldCounter();
    }
}

說明:ThreadLocalHoldCounter重寫了ThreadLocal的initialValue方法,ThreadLocal類可以將線程與對(duì)象相關(guān)聯(lián)钠右。在沒有進(jìn)行set的情況下赋元,get到的均是initialValue方法里面生成的那個(gè)HolderCounter對(duì)象。

(3)類的屬性

abstract static class Sync extends AbstractQueuedSynchronizer {
    // 版本序列號(hào)
    private static final long serialVersionUID = 6317671515068378041L;        
    // 高16位為讀鎖飒房,低16位為寫鎖
    static final int SHARED_SHIFT   = 16;
    // 讀鎖單位
    static final int SHARED_UNIT    = (1 << SHARED_SHIFT);
    // 讀鎖最大數(shù)量
    static final int MAX_COUNT      = (1 << SHARED_SHIFT) - 1;
    // 寫鎖最大數(shù)量
    static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;
    // 本地線程計(jì)數(shù)器
    private transient ThreadLocalHoldCounter readHolds;
    // 緩存的計(jì)數(shù)器
    private transient HoldCounter cachedHoldCounter;
    // 第一個(gè)讀線程
    private transient Thread firstReader = null;
    // 第一個(gè)讀線程的計(jì)數(shù)
    private transient int firstReaderHoldCount;
}

說明:該屬性中包括了讀鎖搁凸、寫鎖線程的最大量。本地線程計(jì)數(shù)器等狠毯。

(4)類的構(gòu)造函數(shù)

// 構(gòu)造函數(shù)
Sync() {
    // 本地線程計(jì)數(shù)器
    readHolds = new ThreadLocalHoldCounter();
    // 設(shè)置AQS的狀態(tài)
    setState(getState()); // ensures visibility of readHolds
}

說明:在Sync的構(gòu)造函數(shù)中設(shè)置了本地線程計(jì)數(shù)器和AQS的狀態(tài)state护糖。

3、讀寫狀態(tài)的設(shè)計(jì)

同步狀態(tài)在重入鎖的實(shí)現(xiàn)中是表示被同一個(gè)線程重復(fù)獲取的次數(shù)嚼松,即一個(gè)整形變量來維護(hù)嫡良,但是之前的那個(gè)表示僅僅表示是否鎖定,而不用區(qū)分是讀鎖還是寫鎖献酗。而讀寫鎖需要在同步狀態(tài)(一個(gè)整形變量)上維護(hù)多個(gè)讀線程和一個(gè)寫線程的狀態(tài)寝受。

讀寫鎖對(duì)于同步狀態(tài)的實(shí)現(xiàn)是在一個(gè)整形變量上通過“按位切割使用”:將變量切割成兩部分,高16位表示讀罕偎,低16位表示寫很澄。

假設(shè)當(dāng)前同步狀態(tài)值為S,get和set的操作如下:

(1)獲取寫狀態(tài):
S&0x0000FFFF:將高16位全部抹去
(2)獲取讀狀態(tài):
S>>>16:無符號(hào)補(bǔ)0颜及,右移16位
(3)寫狀態(tài)加1:
S+1
(4)讀狀態(tài)加1:
  S+(1<<16)即S + 0x00010000
在代碼層的判斷中痴怨,如果S不等于0,當(dāng)寫狀態(tài)(S&0x0000FFFF)器予,而讀狀態(tài)(S>>>16)大于0浪藻,則表示該讀寫鎖的讀鎖已被獲取。

4乾翔、寫鎖的獲取與釋放

看下WriteLock類中的lock和unlock方法:

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

public void unlock() {
    sync.release(1);
}

可以看到就是調(diào)用的獨(dú)占式同步狀態(tài)的獲取與釋放爱葵,因此真實(shí)的實(shí)現(xiàn)就是Sync的 tryAcquire和 tryRelease。

寫鎖的獲取反浓,看下tryAcquire:

protected final boolean tryAcquire(int acquires) {
    //當(dāng)前線程
    Thread current = Thread.currentThread();
    //獲取狀態(tài)
    int c = getState();
    //寫線程數(shù)量(即獲取獨(dú)占鎖的重入數(shù))
    int w = exclusiveCount(c);
    
    //當(dāng)前同步狀態(tài)state != 0萌丈,說明已經(jīng)有其他線程獲取了讀鎖或?qū)戞i
    if (c != 0) {
        // 當(dāng)前state不為0,此時(shí):如果寫鎖狀態(tài)為0說明讀鎖此時(shí)被占用返回false雷则;
        // 如果寫鎖狀態(tài)不為0且寫鎖沒有被當(dāng)前線程持有返回false
        if (w == 0 || current != getExclusiveOwnerThread())
            return false;        
        //判斷同一線程獲取寫鎖是否超過最大次數(shù)(65535)辆雾,支持可重入
        if (w + exclusiveCount(acquires) > MAX_COUNT)
            throw new Error("Maximum lock count exceeded");
        //更新狀態(tài)
        //此時(shí)當(dāng)前線程已持有寫鎖,現(xiàn)在是重入月劈,所以只需要修改鎖的數(shù)量即可度迂。
        setState(c + acquires);
        return true;
    }    
    //到這里說明此時(shí)c=0,讀鎖和寫鎖都沒有被獲取
    //writerShouldBlock表示是否阻塞
    if (writerShouldBlock() ||
        !compareAndSetState(c, c + acquires))
        return false;    
    //設(shè)置鎖為當(dāng)前線程所有
    setExclusiveOwnerThread(current);
    return true;
}

其中exclusiveCount方法表示占有寫鎖的線程數(shù)量藤乙,源碼如下:

static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }
說明:直接將狀態(tài)state和(2^16 - 1)做與運(yùn)算,其等效于將state模上2^16惭墓。寫鎖數(shù)量由state的低十六位表示坛梁。
從源代碼可以看出,獲取寫鎖的步驟如下:
(1)首先獲取c腊凶、w划咐。c表示當(dāng)前鎖狀態(tài);w表示寫線程數(shù)量钧萍。然后判斷同步狀態(tài)state是否為0褐缠。如果state!=0,說明已經(jīng)有其他線程獲取了讀鎖或?qū)戞i风瘦,執(zhí)行(2)送丰;否則執(zhí)行(5)。
(2)如果鎖狀態(tài)不為零(c != 0)弛秋,而寫鎖的狀態(tài)為0(w = 0)器躏,說明讀鎖此時(shí)被其他線程占用,所以當(dāng)前線程不能獲取寫鎖蟹略,自然返回false登失。或者鎖狀態(tài)不為零挖炬,而寫鎖的狀態(tài)也不為0揽浙,但是獲取寫鎖的線程不是當(dāng)前線程,則當(dāng)前線程也不能獲取寫鎖意敛。
(3)判斷當(dāng)前線程獲取寫鎖是否超過最大次數(shù)馅巷,若超過,拋異常草姻,反之更新同步狀態(tài)(此時(shí)當(dāng)前線程已獲取寫鎖钓猬,更新是線程安全的),返回true撩独。
(4)如果state為0敞曹,此時(shí)讀鎖或?qū)戞i都沒有被獲取,判斷是否需要阻塞(公平和非公平方式實(shí)現(xiàn)不同)综膀,在非公平策略下總是不會(huì)被阻塞澳迫,在公平策略下會(huì)進(jìn)行判斷(判斷同步隊(duì)列中是否有等待時(shí)間更長(zhǎng)的線程,若存在剧劝,則需要被阻塞橄登,否則,無需阻塞),如果不需要阻塞拢锹,則CAS更新同步狀態(tài)谣妻,若CAS成功則返回true,失敗則說明鎖被別的線程搶去了面褐,返回false。如果需要阻塞則也返回false取胎。
(5)成功獲取寫鎖后展哭,將當(dāng)前線程設(shè)置為占有寫鎖的線程,返回true闻蛀。

方法流程圖如下:


寫鎖的釋放匪傍,tryRelease方法:

    //若鎖的持有者不是當(dāng)前線程,拋出異常
    if (!isHeldExclusively())
        throw new IllegalMonitorStateException();
    //寫鎖的新線程數(shù)
    int nextc = getState() - releases;
    //如果獨(dú)占模式重入數(shù)為0了觉痛,說明獨(dú)占模式被釋放
    boolean free = exclusiveCount(nextc) == 0;
    if (free)
        //若寫鎖的新線程數(shù)為0役衡,則將鎖的持有者設(shè)置為null
        setExclusiveOwnerThread(null);
    //設(shè)置寫鎖的新線程數(shù)
    //不管獨(dú)占模式是否被釋放,更新獨(dú)占重入數(shù)
    setState(nextc);
    return free;
}

寫鎖的釋放過程還是相對(duì)而言比較簡(jiǎn)單的:首先查看當(dāng)前線程是否為寫鎖的持有者薪棒,如果不是拋出異常手蝎。然后檢查釋放后寫鎖的線程數(shù)是否為0,如果為0則表示寫鎖空閑了俐芯,釋放鎖資源將鎖的持有線程設(shè)置為null棵介,否則釋放僅僅只是一次重入鎖而已,并不能將寫鎖的線程清空吧史。

說明:此方法用于釋放寫鎖資源邮辽,首先會(huì)判斷該線程是否為獨(dú)占線程,若不為獨(dú)占線程贸营,則拋出異常吨述,否則,計(jì)算釋放資源后的寫鎖的數(shù)量钞脂,若為0揣云,表示成功釋放,資源不將被占用冰啃,否則灵再,表示資源還被占用。其方法流程圖如下亿笤。


5翎迁、讀鎖的獲取與釋放

類似于寫鎖,讀鎖的lock和unlock的實(shí)際實(shí)現(xiàn)對(duì)應(yīng)Sync的 tryAcquireShared 和 tryReleaseShared方法净薛。

讀鎖的獲取汪榔,看下tryAcquireShared方法

protected final int tryAcquireShared(int unused) {
    // 獲取當(dāng)前線程
    Thread current = Thread.currentThread();
    // 獲取狀態(tài)
    int c = getState();
    
    //如果寫鎖線程數(shù) != 0 ,且獨(dú)占鎖不是當(dāng)前線程則返回失敗,因?yàn)榇嬖阪i降級(jí)
    if (exclusiveCount(c) != 0 &&
        getExclusiveOwnerThread() != current)
        return -1;
    // 讀鎖數(shù)量
    int r = sharedCount(c);
    /*
     * readerShouldBlock():讀鎖是否需要等待(公平鎖原則)
     * r < MAX_COUNT:持有線程小于最大數(shù)(65535)
     * compareAndSetState(c, c + SHARED_UNIT):設(shè)置讀取鎖狀態(tài)
     */
     // 讀線程是否應(yīng)該被阻塞痴腌、并且小于最大值雌团、并且比較設(shè)置成功
    if (!readerShouldBlock() &&
        r < MAX_COUNT &&
        compareAndSetState(c, c + SHARED_UNIT)) {
        //r == 0,表示第一個(gè)讀鎖線程士聪,第一個(gè)讀鎖firstRead是不會(huì)加入到readHolds中
        if (r == 0) { // 讀鎖數(shù)量為0
            // 設(shè)置第一個(gè)讀線程
            firstReader = current;
            // 讀線程占用的資源數(shù)為1
            firstReaderHoldCount = 1;
        } else if (firstReader == current) { // 當(dāng)前線程為第一個(gè)讀線程锦援,表示第一個(gè)讀鎖線程重入
            // 占用資源數(shù)加1
            firstReaderHoldCount++;
        } else { // 讀鎖數(shù)量不為0并且不為當(dāng)前線程
            // 獲取計(jì)數(shù)器
            HoldCounter rh = cachedHoldCounter;
            // 計(jì)數(shù)器為空或者計(jì)數(shù)器的tid不為當(dāng)前正在運(yùn)行的線程的tid
            if (rh == null || rh.tid != getThreadId(current)) 
                // 獲取當(dāng)前線程對(duì)應(yīng)的計(jì)數(shù)器
                cachedHoldCounter = rh = readHolds.get();
            else if (rh.count == 0) // 計(jì)數(shù)為0
                //加入到readHolds中
                readHolds.set(rh);
            //計(jì)數(shù)+1
            rh.count++;
        }
        return 1;
    }
    return fullTryAcquireShared(current);
}

其中sharedCount方法表示占有讀鎖的線程數(shù)量,源碼如下:

<pre style="margin: 0px; padding: 0px; white-space: pre-wrap; overflow-wrap: break-word; font-family: "Courier New" !important; font-size: 12px !important;">static int sharedCount(int c) { return c >>> SHARED_SHIFT; }</pre>

說明:直接將state右移16位剥悟,就可以得到讀鎖的線程數(shù)量灵寺,因?yàn)閟tate的高16位表示讀鎖,對(duì)應(yīng)的第十六位表示寫鎖數(shù)量区岗。

讀鎖獲取鎖的過程比寫鎖稍微復(fù)雜些略板,首先判斷寫鎖是否為0并且當(dāng)前線程不占有獨(dú)占鎖,直接返回慈缔;否則叮称,判斷讀線程是否需要被阻塞并且讀鎖數(shù)量是否小于最大值并且比較設(shè)置狀態(tài)成功,若當(dāng)前沒有讀鎖藐鹤,則設(shè)置第一個(gè)讀線程firstReader和firstReaderHoldCount瓤檐;若當(dāng)前線程線程為第一個(gè)讀線程,則增加firstReaderHoldCount娱节;否則距帅,將設(shè)置當(dāng)前線程對(duì)應(yīng)的HoldCounter對(duì)象的值。流程圖如下括堤。

注意:更新成功后會(huì)在firstReaderHoldCount中或readHolds(ThreadLocal類型的)的本線程副本中記錄當(dāng)前線程重入數(shù)(23行至43行代碼)碌秸,這是為了實(shí)現(xiàn)jdk1.6中加入的getReadHoldCount()方法的,這個(gè)方法能獲取當(dāng)前線程重入共享鎖的次數(shù)(state中記錄的是多個(gè)線程的總重入次數(shù))悄窃,加入了這個(gè)方法讓代碼復(fù)雜了不少讥电,但是其原理還是很簡(jiǎn)單的:如果當(dāng)前只有一個(gè)線程的話,還不需要?jiǎng)佑肨hreadLocal轧抗,直接往firstReaderHoldCount這個(gè)成員變量里存重入數(shù)恩敌,當(dāng)有第二個(gè)線程來的時(shí)候,就要?jiǎng)佑肨hreadLocal變量readHolds了横媚,每個(gè)線程擁有自己的副本纠炮,用來保存自己的重入數(shù)。

fullTryAcquireShared方法:

final int fullTryAcquireShared(Thread current) {

    HoldCounter rh = null;
    for (;;) { // 無限循環(huán)
        // 獲取狀態(tài)
        int c = getState();
        if (exclusiveCount(c) != 0) { // 寫線程數(shù)量不為0
            if (getExclusiveOwnerThread() != current) // 不為當(dāng)前線程
                return -1;
        } else if (readerShouldBlock()) { // 寫線程數(shù)量為0并且讀線程被阻塞
            // Make sure we're not acquiring read lock reentrantly
            if (firstReader == current) { // 當(dāng)前線程為第一個(gè)讀線程
                // assert firstReaderHoldCount > 0;
            } else { // 當(dāng)前線程不為第一個(gè)讀線程
                if (rh == null) { // 計(jì)數(shù)器不為空
                    // 
                    rh = cachedHoldCounter;
                    if (rh == null || rh.tid != getThreadId(current)) { // 計(jì)數(shù)器為空或者計(jì)數(shù)器的tid不為當(dāng)前正在運(yùn)行的線程的tid
                        rh = readHolds.get();
                        if (rh.count == 0)
                            readHolds.remove();
                    }
                }
                if (rh.count == 0)
                    return -1;
            }
        }
        if (sharedCount(c) == MAX_COUNT) // 讀鎖數(shù)量為最大值灯蝴,拋出異常
            throw new Error("Maximum lock count exceeded");
        if (compareAndSetState(c, c + SHARED_UNIT)) { // 比較并且設(shè)置成功
            if (sharedCount(c) == 0) { // 讀線程數(shù)量為0
                // 設(shè)置第一個(gè)讀線程
                firstReader = current;
                // 
                firstReaderHoldCount = 1;
            } else if (firstReader == current) {
                firstReaderHoldCount++;
            } else {
                if (rh == null)
                    rh = cachedHoldCounter;
                if (rh == null || rh.tid != getThreadId(current))
                    rh = readHolds.get();
                else if (rh.count == 0)
                    readHolds.set(rh);
                rh.count++;
                cachedHoldCounter = rh; // cache for release
            }
            return 1;
        }
    }
}

說明:在tryAcquireShared函數(shù)中恢口,如果下列三個(gè)條件不滿足(讀線程是否應(yīng)該被阻塞、小于最大值穷躁、比較設(shè)置成功)則會(huì)進(jìn)行fullTryAcquireShared函數(shù)中耕肩,它用來保證相關(guān)操作可以成功。其邏輯與tryAcquireShared邏輯類似,不再累贅猿诸。

讀鎖的釋放婚被,tryReleaseShared方法

protected final boolean tryReleaseShared(int unused) {
    // 獲取當(dāng)前線程
    Thread current = Thread.currentThread();
    if (firstReader == current) { // 當(dāng)前線程為第一個(gè)讀線程
        // assert firstReaderHoldCount > 0;
        if (firstReaderHoldCount == 1) // 讀線程占用的資源數(shù)為1
            firstReader = null;
        else // 減少占用的資源
            firstReaderHoldCount--;
    } else { // 當(dāng)前線程不為第一個(gè)讀線程
        // 獲取緩存的計(jì)數(shù)器
        HoldCounter rh = cachedHoldCounter;
        if (rh == null || rh.tid != getThreadId(current)) // 計(jì)數(shù)器為空或者計(jì)數(shù)器的tid不為當(dāng)前正在運(yùn)行的線程的tid
            // 獲取當(dāng)前線程對(duì)應(yīng)的計(jì)數(shù)器
            rh = readHolds.get();
        // 獲取計(jì)數(shù)
        int count = rh.count;
        if (count <= 1) { // 計(jì)數(shù)小于等于1
            // 移除
            readHolds.remove();
            if (count <= 0) // 計(jì)數(shù)小于等于0,拋出異常
                throw unmatchedUnlockException();
        }
        // 減少計(jì)數(shù)
        --rh.count;
    }
    for (;;) { // 無限循環(huán)
        // 獲取狀態(tài)
        int c = getState();
        // 獲取狀態(tài)
        int nextc = c - SHARED_UNIT;
        if (compareAndSetState(c, nextc)) // 比較并進(jìn)行設(shè)置
            // Releasing the read lock has no effect on readers,
            // but it may allow waiting writers to proceed if
            // both read and write locks are now free.
            return nextc == 0;
    }
}

說明:此方法表示讀鎖線程釋放鎖梳虽。首先判斷當(dāng)前線程是否為第一個(gè)讀線程firstReader址芯,若是,則判斷第一個(gè)讀線程占有的資源數(shù)firstReaderHoldCount是否為1窜觉,若是谷炸,則設(shè)置第一個(gè)讀線程firstReader為空,否則竖螃,將第一個(gè)讀線程占有的資源數(shù)firstReaderHoldCount減1淑廊;若當(dāng)前線程不是第一個(gè)讀線程逗余,那么首先會(huì)獲取緩存計(jì)數(shù)器(上一個(gè)讀鎖線程對(duì)應(yīng)的計(jì)數(shù)器 )特咆,若計(jì)數(shù)器為空或者tid不等于當(dāng)前線程的tid值,則獲取當(dāng)前線程的計(jì)數(shù)器录粱,如果計(jì)數(shù)器的計(jì)數(shù)count小于等于1腻格,則移除當(dāng)前線程對(duì)應(yīng)的計(jì)數(shù)器,如果計(jì)數(shù)器的計(jì)數(shù)count小于等于0啥繁,則拋出異常菜职,之后再減少計(jì)數(shù)即可。無論何種情況旗闽,都會(huì)進(jìn)入無限循環(huán)酬核,該循環(huán)可以確保成功設(shè)置狀態(tài)state。其流程圖如下适室。

在讀鎖的獲取嫡意、釋放過程中,總是會(huì)有一個(gè)對(duì)象存在著捣辆,同時(shí)該對(duì)象在獲取線程獲取讀鎖是+1蔬螟,釋放讀鎖時(shí)-1,該對(duì)象就是HoldCounter汽畴。

要明白HoldCounter就要先明白讀鎖旧巾。前面提過讀鎖的內(nèi)在實(shí)現(xiàn)機(jī)制就是共享鎖,對(duì)于共享鎖其實(shí)我們可以稍微的認(rèn)為它不是一個(gè)鎖的概念忍些,它更加像一個(gè)計(jì)數(shù)器的概念鲁猩。一次共享鎖操作就相當(dāng)于一次計(jì)數(shù)器的操作,獲取共享鎖計(jì)數(shù)器+1罢坝,釋放共享鎖計(jì)數(shù)器-1绳匀。只有當(dāng)線程獲取共享鎖后才能對(duì)共享鎖進(jìn)行釋放、重入操作。所以HoldCounter的作用就是當(dāng)前線程持有共享鎖的數(shù)量疾棵,這個(gè)數(shù)量必須要與線程綁定在一起戈钢,否則操作其他線程鎖就會(huì)拋出異常。

先看讀鎖獲取鎖的部分:

if (r == 0) {//r == 0是尔,表示第一個(gè)讀鎖線程殉了,第一個(gè)讀鎖firstRead是不會(huì)加入到readHolds中
    firstReader = current;
    firstReaderHoldCount = 1;
} else if (firstReader == current) {//第一個(gè)讀鎖線程重入
    firstReaderHoldCount++;    
} else {    //非firstReader計(jì)數(shù)
    HoldCounter rh = cachedHoldCounter;//readHoldCounter緩存
    //rh == null 或者 rh.tid != current.getId(),需要獲取rh
    if (rh == null || rh.tid != current.getId())    
        cachedHoldCounter = rh = readHolds.get();
    else if (rh.count == 0)
        readHolds.set(rh);  //加入到readHolds中
    rh.count++; //計(jì)數(shù)+1
}

這里為什么要搞一個(gè)firstRead拟枚、firstReaderHoldCount呢薪铜?而不是直接使用else那段代碼?這是為了一個(gè)效率問題恩溅,firstReader是不會(huì)放入到readHolds中的隔箍,如果讀鎖僅有一個(gè)的情況下就會(huì)避免查找readHolds〗畔纾可能就看這個(gè)代碼還不是很理解HoldCounter蜒滩。我們先看firstReader、firstReaderHoldCount的定義:

private transient Thread firstReader = null;
private transient int firstReaderHoldCount;

這兩個(gè)變量比較簡(jiǎn)單奶稠,一個(gè)表示線程嘶炭,當(dāng)然該線程是一個(gè)特殊的線程得运,一個(gè)是firstReader的重入計(jì)數(shù)。

HoldCounter的定義:
static final class HoldCounter {
    int count = 0;
    final long tid = Thread.currentThread().getId();
}

在HoldCounter中僅有count和tid兩個(gè)變量,其中count代表著計(jì)數(shù)器信殊,tid是線程的id诺苹。但是如果要將一個(gè)對(duì)象和線程綁定起來僅記錄tid肯定不夠的抛姑,而且HoldCounter根本不能起到綁定對(duì)象的作用卓嫂,只是記錄線程tid而已。

誠(chéng)然蜈项,在java中芹关,我們知道如果要將一個(gè)線程和對(duì)象綁定在一起只有ThreadLocal才能實(shí)現(xiàn)。所以如下:

static final class ThreadLocalHoldCounter
    extends ThreadLocal<HoldCounter> {
    public HoldCounter initialValue() {
        return new HoldCounter();
    }
}

ThreadLocalHoldCounter繼承ThreadLocal战得,并且重寫了initialValue方法充边。

故而,HoldCounter應(yīng)該就是綁定線程上的一個(gè)計(jì)數(shù)器常侦,而ThradLocalHoldCounter則是線程綁定的ThreadLocal浇冰。從上面我們可以看到ThreadLocal將HoldCounter綁定到當(dāng)前線程上,同時(shí)HoldCounter也持有線程Id聋亡,這樣在釋放鎖的時(shí)候才能知道ReadWriteLock里面緩存的上一個(gè)讀取線程(cachedHoldCounter)是否是當(dāng)前線程肘习。這樣做的好處是可以減少ThreadLocal.get()的次數(shù),因?yàn)檫@也是一個(gè)耗時(shí)操作坡倔。需要說明的是這樣HoldCounter綁定線程id而不綁定線程對(duì)象的原因是避免HoldCounter和ThreadLocal互相綁定而GC難以釋放它們(盡管GC能夠智能的發(fā)現(xiàn)這種引用而回收它們漂佩,但是這需要一定的代價(jià))脖含,所以其實(shí)這樣做只是為了幫助GC快速回收對(duì)象而已。

三投蝉、總結(jié)

通過上面的源碼分析养葵,我們可以發(fā)現(xiàn)一個(gè)現(xiàn)象:

在線程持有讀鎖的情況下,該線程不能取得寫鎖(因?yàn)楂@取寫鎖的時(shí)候瘩缆,如果發(fā)現(xiàn)當(dāng)前的讀鎖被占用关拒,就馬上獲取失敗,不管讀鎖是不是被當(dāng)前線程持有)庸娱。

在線程持有寫鎖的情況下着绊,該線程可以繼續(xù)獲取讀鎖(獲取讀鎖時(shí)如果發(fā)現(xiàn)寫鎖被占用,只有寫鎖沒有被當(dāng)前線程占用的情況才會(huì)獲取失斒煳尽)归露。

仔細(xì)想想,這個(gè)設(shè)計(jì)是合理的:因?yàn)楫?dāng)線程獲取讀鎖的時(shí)候斤儿,可能有其他線程同時(shí)也在持有讀鎖剧包,因此不能把獲取讀鎖的線程“升級(jí)”為寫鎖;而對(duì)于獲得寫鎖的線程雇毫,它一定獨(dú)占了讀寫鎖玄捕,因此可以繼續(xù)讓它獲取讀鎖踩蔚,當(dāng)它同時(shí)獲取了寫鎖和讀鎖后棚放,還可以先釋放寫鎖繼續(xù)持有讀鎖,這樣一個(gè)寫鎖就“降級(jí)”為了讀鎖馅闽。

綜上:

一個(gè)線程要想同時(shí)持有寫鎖和讀鎖飘蚯,必須先獲取寫鎖再獲取讀鎖;寫鎖可以“降級(jí)”為讀鎖福也;讀鎖不能“升級(jí)”為寫鎖局骤。
謝謝你的閱讀,如果您覺得這篇博文對(duì)你有幫助暴凑,請(qǐng)點(diǎn)贊或者喜歡峦甩,讓更多的人看到!祝你每天開心愉快现喳!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末凯傲,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子嗦篱,更是在濱河造成了極大的恐慌冰单,老刑警劉巖,帶你破解...
    沈念sama閱讀 207,113評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件灸促,死亡現(xiàn)場(chǎng)離奇詭異诫欠,居然都是意外死亡涵卵,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門荒叼,熙熙樓的掌柜王于貴愁眉苦臉地迎上來轿偎,“玉大人,你說我怎么就攤上這事被廓√颍” “怎么了?”我有些...
    開封第一講書人閱讀 153,340評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵伊者,是天一觀的道長(zhǎng)英遭。 經(jīng)常有香客問我,道長(zhǎng)亦渗,這世上最難降的妖魔是什么挖诸? 我笑而不...
    開封第一講書人閱讀 55,449評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮法精,結(jié)果婚禮上多律,老公的妹妹穿的比我還像新娘。我一直安慰自己搂蜓,他們只是感情好狼荞,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評(píng)論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著帮碰,像睡著了一般相味。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上殉挽,一...
    開封第一講書人閱讀 49,166評(píng)論 1 284
  • 那天丰涉,我揣著相機(jī)與錄音,去河邊找鬼斯碌。 笑死一死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的傻唾。 我是一名探鬼主播投慈,決...
    沈念sama閱讀 38,442評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼冠骄!你這毒婦竟也來了伪煤?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,105評(píng)論 0 261
  • 序言:老撾萬榮一對(duì)情侶失蹤猴抹,失蹤者是張志新(化名)和其女友劉穎带族,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體蟀给,經(jīng)...
    沈念sama閱讀 43,601評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡蝙砌,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評(píng)論 2 325
  • 正文 我和宋清朗相戀三年阳堕,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片择克。...
    茶點(diǎn)故事閱讀 38,161評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡恬总,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出肚邢,到底是詐尸還是另有隱情壹堰,我是刑警寧澤,帶...
    沈念sama閱讀 33,792評(píng)論 4 323
  • 正文 年R本政府宣布骡湖,位于F島的核電站贱纠,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏响蕴。R本人自食惡果不足惜谆焊,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望浦夷。 院中可真熱鬧辖试,春花似錦、人聲如沸劈狐。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽肥缔。三九已至莲兢,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間辫继,已是汗流浹背怒见。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評(píng)論 1 261
  • 我被黑心中介騙來泰國(guó)打工俗慈, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留姑宽,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,618評(píng)論 2 355
  • 正文 我出身青樓闺阱,卻偏偏與公主長(zhǎng)得像炮车,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子酣溃,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評(píng)論 2 344

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