亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

Java中的讀寫鎖ReentrantReadWriteLock源碼分析

 更新時(shí)間:2023年12月06日 10:58:30   作者:洋洋yang羊  
這篇文章主要介紹了Java中的讀寫鎖ReentrantReadWriteLock源碼分析,ReentrantReadWriteLock 分為讀鎖和寫鎖兩個(gè)實(shí)例,讀鎖是共享鎖,可被多個(gè)線程同時(shí)使用,寫鎖是獨(dú)占鎖,持有寫鎖的線程可以繼續(xù)獲取讀鎖,反之不行,需要的朋友可以參考下

使用示例

下面這個(gè)例子非常實(shí)用,我是 javadoc 的搬運(yùn)工:

// 這是一個(gè)關(guān)于緩存操作的故事
class CachedData {
    Object data;
    volatile boolean cacheValid;
    // 讀寫鎖實(shí)例
    final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();

    void processCachedData() {
        // 獲取讀鎖
        rwl.readLock().lock();
        if (!cacheValid) { // 如果緩存過期了,或者為 null
            // 釋放掉讀鎖,然后獲取寫鎖 (后面會看到,沒釋放掉讀鎖就獲取寫鎖,會發(fā)生死鎖情況)
            rwl.readLock().unlock();
            rwl.writeLock().lock();
            
            try {
                if (!cacheValid) { // 重新判斷,因?yàn)樵诘却龑戞i的過程中,可能前面有其他寫線程執(zhí)行過了
                    data = ...
                    cacheValid = true;
                }
                // 獲取讀鎖 (持有寫鎖的情況下,是允許獲取讀鎖的,稱為 “鎖降級”,反之不行。)
                rwl.readLock().lock();
            } finally {
                // 釋放寫鎖,此時(shí)還剩一個(gè)讀鎖
                rwl.writeLock().unlock(); // Unlock write, still hold read
            }
        }

        try {
            use(data);
        } finally {
            // 釋放讀鎖
            rwl.readLock().unlock();
        }
    }
}

ReentrantReadWriteLock 分為讀鎖和寫鎖兩個(gè)實(shí)例,讀鎖是共享鎖,可被多個(gè)線程同時(shí)使用,寫鎖是獨(dú)占鎖。持有寫鎖的線程可以繼續(xù)獲取讀鎖,反之不行。

ReentrantReadWriteLock 總覽

這一節(jié)比較重要,我們要先看清楚 ReentrantReadWriteLock 的大框架,然后再到源碼細(xì)節(jié)。

首先,我們來看下 ReentrantReadWriteLock 的結(jié)構(gòu),它有好些嵌套類:

11

大家先仔細(xì)看看這張圖中的信息。然后我們把 ReadLock 和 WriteLock 的代碼提出來一起看,清晰一些:

12

很清楚了,ReadLock 和 WriteLock 中的方法都是通過 Sync 這個(gè)類來實(shí)現(xiàn)的。Sync 是 AQS 的子類,然后再派生了公平模式和不公平模式。

從它們調(diào)用的 Sync 方法,我們可以看到: ReadLock 使用了共享模式,WriteLock 使用了獨(dú)占模式。

等等,同一個(gè) AQS 實(shí)例怎么可以同時(shí)使用共享模式和獨(dú)占模式???

這里給大家回顧下 AQS,我們橫向?qū)Ρ认?AQS 的共享模式和獨(dú)占模式:

13

AQS 的精髓在于內(nèi)部的屬性 state

對于獨(dú)占模式來說,通常就是 0 代表可獲取鎖,1 代表鎖被別人獲取了,重入例外而共享模式下,每個(gè)線程都可以對 state 進(jìn)行加減操作

也就是說,獨(dú)占模式和共享模式對于 state 的操作完全不一樣,那讀寫鎖 ReentrantReadWriteLock 中是怎么使用 state 的呢?答案是將 state 這個(gè) 32 位的 int 值分為高 16 位和低 16位,分別用于共享模式和獨(dú)占模式。

源碼分析

有了前面的概念,大家心里應(yīng)該都有數(shù)了吧,下面就不再那么啰嗦了,直接代碼分析。

源代碼加注釋 1500 行,并不算難,我們要看的代碼量不大。如果你前面一節(jié)都理解了,那么直接從頭開始一行一行往下看就是了,還是比較簡單的。

ReentrantReadWriteLock 的前面幾行很簡單,我們往下滑到 Sync 類,先來看下它的所有的屬性:

abstract static class Sync extends AbstractQueuedSynchronizer {
    // 下面這塊說的就是將 state 一分為二,高 16 位用于共享模式,低16位用于獨(dú)占模式
    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;
    // 取 c 的高 16 位值,代表讀鎖的獲取次數(shù)(包括重入)
    static int sharedCount(int c)    { return c >>> SHARED_SHIFT; }
    // 取 c 的低 16 位值,代表寫鎖的重入次數(shù),因?yàn)閷戞i是獨(dú)占模式
    static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }

    // 這個(gè)嵌套類的實(shí)例用來記錄每個(gè)線程持有的讀鎖數(shù)量(讀鎖重入)
    static final class HoldCounter {
        // 持有的讀鎖數(shù)
        int count = 0;
        // 線程 id
        final long tid = getThreadId(Thread.currentThread());
    }

    // ThreadLocal 的子類
    static final class ThreadLocalHoldCounter
        extends ThreadLocal<HoldCounter> {
        public HoldCounter initialValue() {
            return new HoldCounter();
        }
    }
    /**
      * 組合使用上面兩個(gè)類,用一個(gè) ThreadLocal 來記錄當(dāng)前線程持有的讀鎖數(shù)量
      */ 
    private transient ThreadLocalHoldCounter readHolds;

    // 用于緩存,記錄"最后一個(gè)獲取讀鎖的線程"的讀鎖重入次數(shù),
    // 所以不管哪個(gè)線程獲取到讀鎖后,就把這個(gè)值占為已用,這樣就不用到 ThreadLocal 中查詢 map 了
    // 算不上理論的依據(jù):通常讀鎖的獲取很快就會伴隨著釋放,
    //   顯然,在 獲取->釋放 讀鎖這段時(shí)間,如果沒有其他線程獲取讀鎖的話,此緩存就能幫助提高性能
    private transient HoldCounter cachedHoldCounter;
    
    // 第一個(gè)獲取讀鎖的線程(并且其未釋放讀鎖),以及它持有的讀鎖數(shù)量
    private transient Thread firstReader = null;
    private transient int firstReaderHoldCount;

    Sync() {
        // 初始化 readHolds 這個(gè) ThreadLocal 屬性
        readHolds = new ThreadLocalHoldCounter();
        // 為了保證 readHolds 的內(nèi)存可見性
        setState(getState()); // ensures visibility of readHolds
    }
    ...
}
  1. state 的高 16 位代表讀鎖的獲取次數(shù),包括重入次數(shù),獲取到讀鎖一次加 1,釋放掉讀鎖一次減 1
  2. state 的低 16 位代表寫鎖的獲取次數(shù),因?yàn)閷戞i是獨(dú)占鎖,同時(shí)只能被一個(gè)線程獲得,所以它代表重入次數(shù)
  3. 每個(gè)線程都需要維護(hù)自己的 HoldCounter,記錄該線程獲取的讀鎖次數(shù),這樣才能知道到底是不是讀鎖重入,用 ThreadLocal 屬性 readHolds 維護(hù)
  4. cachedHoldCounter 有什么用?其實(shí)沒什么用,但能提示性能。將最后一次獲取讀鎖的線程的 HoldCounter 緩存到這里,這樣比使用 ThreadLocal 性能要好一些,因?yàn)?ThreadLocal 內(nèi)部是基于 map 來查詢的。但是 cachedHoldCounter 這一個(gè)屬性畢竟只能緩存一個(gè)線程,所以它要起提升性能作用的依據(jù)就是:通常讀鎖的獲取緊隨著就是該讀鎖的釋放。我這里可能表達(dá)不太好,但是大家應(yīng)該是懂的吧。
  5. firstReader 和 firstReaderHoldCount 有什么用?其實(shí)也沒什么用,但是它也能提示性能。將"第一個(gè)"獲取讀鎖的線程記錄在 firstReader 屬性中,這里的第一個(gè)不是全局的概念,等這個(gè) firstReader 當(dāng)前代表的線程釋放掉讀鎖以后,會有后來的線程占用這個(gè)屬性的。firstReader 和 firstReaderHoldCount 使得在讀鎖不產(chǎn)生競爭的情況下,記錄讀鎖重入次數(shù)非常方便快速
  6. 如果一個(gè)線程使用了 firstReader,那么它就不需要占用 cachedHoldCounter
  7. 個(gè)人認(rèn)為,讀寫鎖源碼中最讓初學(xué)者頭疼的就是這幾個(gè)用于提升性能的屬性了,使得大家看得云里霧里的。主要是因?yàn)?ThreadLocal 內(nèi)部是通過一個(gè) ThreadLocalMap 來操作的,會增加檢索時(shí)間。而很多場景下,執(zhí)行 unlock 的線程往往就是剛剛最后一次執(zhí)行 lock 的線程,中間可能沒有其他線程進(jìn)行 lock。還有就是很多不怎么會發(fā)生讀鎖競爭的場景。

上面說了這么多,是希望能幫大家降低后面閱讀源碼的壓力,大家也可以先看看后面的,然后再慢慢體會。

前面我們好像都只說讀鎖,完全沒提到寫鎖,主要是因?yàn)閷戞i真的是簡單很多,我也特地將寫鎖的源碼放到了后面,我們先啃下最難的讀鎖先。

讀鎖獲取

下面我就不一行一行按源碼順序說了,我們按照使用來說。

我們來看下讀鎖 ReadLock 的 lock 流程:

// ReadLock
public void lock() {
    sync.acquireShared(1);
}
// AQS
public final void acquireShared(int arg) {
    if (tryAcquireShared(arg) < 0)
        doAcquireShared(arg);
}

然后我們就會進(jìn)到 Sync 類的 tryAcquireShared 方法:

在 AQS 中,如果 tryAcquireShared(arg) 方法返回值小于 0 代表沒有獲取到共享鎖(讀鎖),大于 0 代表獲取到

回顧 AQS 共享模式:tryAcquireShared 方法不僅僅在 acquireShared 的最開始被使用,這里是 try,也就可能會失敗,如果失敗的話,執(zhí)行后面的 doAcquireShared,進(jìn)入到阻塞隊(duì)列,然后等待前驅(qū)節(jié)點(diǎn)喚醒。喚醒以后,還是會調(diào)用 tryAcquireShared 進(jìn)行獲取共享鎖的。當(dāng)然,喚醒以后再 try 是很容易獲得鎖的,因?yàn)檫@個(gè)節(jié)點(diǎn)已經(jīng)排了很久的隊(duì)了,組織是會照顧它的。

所以,你在看下面這段代碼的時(shí)候,要想象到兩種獲取讀鎖的場景,一種是新來的,一種是排隊(duì)排到它的。

protected final int tryAcquireShared(int unused) {

    Thread current = Thread.currentThread();
    int c = getState();
    
    // exclusiveCount(c) 不等于 0,說明有線程持有寫鎖,
    //    而且不是當(dāng)前線程持有寫鎖,那么當(dāng)前線程獲取讀鎖失敗
    // 		(另,如果持有寫鎖的是當(dāng)前線程,是可以繼續(xù)獲取讀鎖的)
    if (exclusiveCount(c) != 0 &&
        getExclusiveOwnerThread() != current)
        return -1;
    
    // 讀鎖的獲取次數(shù)
    int r = sharedCount(c);
    
    // 讀鎖獲取是否需要被阻塞,稍后細(xì)說。為了進(jìn)去下面的分支,假設(shè)這里不阻塞就好了
    if (!readerShouldBlock() &&
        // 判斷是否會溢出 (2^16-1,沒那么容易溢出的)
        r < MAX_COUNT &&
        // 下面這行 CAS 是將 state 屬性的高 16 位加 1,低 16 位不變,如果成功就代表獲取到了讀鎖
        compareAndSetState(c, c + SHARED_UNIT)) {
        
        // =======================
        //   進(jìn)到這里就是獲取到了讀鎖
        // =======================
       
        if (r == 0) {
            // r == 0 說明此線程是第一個(gè)獲取讀鎖的,或者說在它前面獲取讀鎖的都走光光了,它也算是第一個(gè)吧
            //  記錄 firstReader 為當(dāng)前線程,及其持有的讀鎖數(shù)量:1
            firstReader = current;
            firstReaderHoldCount = 1;
        } else if (firstReader == current) {
            // 進(jìn)來這里,說明是 firstReader 重入獲取讀鎖(這非常簡單,count 加 1 結(jié)束)
            firstReaderHoldCount++;
        } else {
            // 前面我們說了 cachedHoldCounter 用于緩存最后一個(gè)獲取讀鎖的線程
            // 如果 cachedHoldCounter 緩存的不是當(dāng)前線程,設(shè)置為緩存當(dāng)前線程的 HoldCounter
            HoldCounter rh = cachedHoldCounter;
            if (rh == null || rh.tid != getThreadId(current))
                cachedHoldCounter = rh = readHolds.get();
            else if (rh.count == 0) 
                // 到這里,那么就是 cachedHoldCounter 緩存的是當(dāng)前線程,但是 count 為 0,
                // 大家可以思考一下:這里為什么要 set ThreadLocal 呢?(當(dāng)然,答案肯定不在這塊代碼中)
                //   既然 cachedHoldCounter 緩存的是當(dāng)前線程,
                //   當(dāng)前線程肯定調(diào)用過 readHolds.get() 進(jìn)行初始化 ThreadLocal
                readHolds.set(rh);
            
            // count 加 1
            rh.count++;
        }
        // return 大于 0 的數(shù),代表獲取到了共享鎖
        return 1;
    }
    // 往下看
    return fullTryAcquireShared(current);
}

上面的代碼中,要進(jìn)入 if 分支,需要滿足:readerShouldBlock() 返回 false,并且 CAS 要成功(我們先不要糾結(jié) MAX_COUNT 溢出)。

那我們反向推,怎么樣進(jìn)入到最后的 fullTryAcquireShared:

readerShouldBlock() 返回 true,2 種情況:

在 FairSync 中說的是 hasQueuedPredecessors(),即阻塞隊(duì)列中有其他元素在等待鎖。

也就是說,公平模式下,有人在排隊(duì)呢,你新來的不能直接獲取鎖

在 NonFairSync 中說的是 apparentlyFirstQueuedIsExclusive(),即判斷阻塞隊(duì)列中 head 的第一個(gè)后繼節(jié)點(diǎn)是否是來獲取寫鎖的,如果是的話,讓這個(gè)寫鎖先來,避免寫鎖饑餓。

作者給寫鎖定義了更高的優(yōu)先級,所以如果碰上獲取寫鎖的線程馬上就要獲取到鎖了,獲取讀鎖的線程不應(yīng)該和它搶。

如果 head.next 不是來獲取寫鎖的,那么可以隨便搶,因?yàn)槭欠枪侥J?,大家比?CAS 速度

compareAndSetState(c, c + SHARED_UNIT) 這里 CAS 失敗,存在競爭??赡苁呛土硪粋€(gè)讀鎖獲取競爭,當(dāng)然也可能是和另一個(gè)寫鎖獲取操作競爭。

然后就會來到 fullTryAcquireShared 中再次嘗試:

/**
 * 1. 剛剛我們說了可能是因?yàn)?CAS 失敗,如果就此返回,那么就要進(jìn)入到阻塞隊(duì)列了,
 *    想想有點(diǎn)不甘心,因?yàn)槎家呀?jīng)滿足了 !readerShouldBlock(),也就是說本來可以不用到阻塞隊(duì)列的,
 *    所以進(jìn)到這個(gè)方法其實(shí)是增加 CAS 成功的機(jī)會
 * 2. 在 NonFairSync 情況下,雖然 head.next 是獲取寫鎖的,我知道它等待很久了,我沒想和它搶,
 *    可是如果我是來重入讀鎖的,那么只能表示對不起了
 */
final int fullTryAcquireShared(Thread current) {
    HoldCounter rh = null;
    // 別忘了這外層有個(gè) for 循環(huán)
    for (;;) {
        int c = getState();
        // 如果其他線程持有了寫鎖,自然這次是獲取不到讀鎖了,乖乖到阻塞隊(duì)列排隊(duì)吧
        if (exclusiveCount(c) != 0) {
            if (getExclusiveOwnerThread() != current)
                return -1;
            // else we hold the exclusive lock; blocking here
            // would cause deadlock.
        } else if (readerShouldBlock()) {
            /**
              * 進(jìn)來這里,說明:
              *  1. exclusiveCount(c) == 0:寫鎖沒有被占用
              *  2. readerShouldBlock() 為 true,說明阻塞隊(duì)列中有其他線程在等待
              *
              * 既然 should block,那進(jìn)來這里是干什么的呢?
              * 答案:是進(jìn)來處理讀鎖重入的!
              * 
              */
            
            // firstReader 線程重入讀鎖,直接到下面的 CAS
            if (firstReader == current) {
                // assert firstReaderHoldCount > 0;
            } else {
                if (rh == null) {
                    rh = cachedHoldCounter;
                    if (rh == null || rh.tid != getThreadId(current)) {
                        // cachedHoldCounter 緩存的不是當(dāng)前線程
                        // 那么到 ThreadLocal 中獲取當(dāng)前線程的 HoldCounter
                        // 如果當(dāng)前線程從來沒有初始化過 ThreadLocal 中的值,get() 會執(zhí)行初始化
                        rh = readHolds.get();
                        // 如果發(fā)現(xiàn) count == 0,也就是說,純屬上一行代碼初始化的,那么執(zhí)行 remove
                        // 然后往下兩三行,乖乖排隊(duì)去
                        if (rh.count == 0)
                            readHolds.remove();
                    }
                }
                if (rh.count == 0)
                    // 排隊(duì)去。
                    return -1;
            }
            /**
              * 這塊代碼我看了蠻久才把握好它是干嘛的,原來只需要知道,它是處理重入的就可以了。
              * 就是為了確保讀鎖重入操作能成功,而不是被塞到阻塞隊(duì)列中等待
              *
              * 另一個(gè)信息就是,這里對于 ThreadLocal 變量 readHolds 的處理:
              *    如果 get() 后發(fā)現(xiàn) count == 0,居然會做 remove() 操作,
              *    這行代碼對于理解其他代碼是有幫助的
              */
        }
        
        if (sharedCount(c) == MAX_COUNT)
            throw new Error("Maximum lock count exceeded");
        
        if (compareAndSetState(c, c + SHARED_UNIT)) {
            // 這里 CAS 成功,那么就意味著成功獲取讀鎖了
            // 下面需要做的是設(shè)置 firstReader 或 cachedHoldCounter
            
            if (sharedCount(c) == 0) {
                // 如果發(fā)現(xiàn) sharedCount(c) 等于 0,就將當(dāng)前線程設(shè)置為 firstReader
                firstReader = current;
                firstReaderHoldCount = 1;
            } else if (firstReader == current) {
                firstReaderHoldCount++;
            } else {
                // 下面這幾行,就是將 cachedHoldCounter 設(shè)置為當(dāng)前線程
                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;
            }
            // 返回大于 0 的數(shù),代表獲取到了讀鎖
            return 1;
        }
    }
}

firstReader 是每次將讀鎖獲取次數(shù)從 0 變?yōu)?1 的那個(gè)線程。

能緩存到 firstReader 中就不要緩存到 cachedHoldCounter 中。

上面的源碼分析應(yīng)該說得非常詳細(xì)了,如果到這里你不太能看懂上面的有些地方的注釋,那么可以先往后看,然后再多看幾遍。

讀鎖釋放

下面我們看看讀鎖釋放的流程:

// ReadLock
public void unlock() {
    sync.releaseShared(1);
}
// Sync
public final boolean releaseShared(int arg) {
    if (tryReleaseShared(arg)) {
        doReleaseShared(); // 這句代碼其實(shí)喚醒 獲取寫鎖的線程,往下看就知道了
        return true;
    }
    return false;
}

// Sync
protected final boolean tryReleaseShared(int unused) {
    Thread current = Thread.currentThread();
    if (firstReader == current) {
        if (firstReaderHoldCount == 1)
            // 如果等于 1,那么這次解鎖后就不再持有鎖了,把 firstReader 置為 null,給后來的線程用
            // 為什么不順便設(shè)置 firstReaderHoldCount = 0?因?yàn)闆]必要,其他線程使用的時(shí)候自己會設(shè)值
            firstReader = null;
        else
            firstReaderHoldCount--;
    } else {
        // 判斷 cachedHoldCounter 是否緩存的是當(dāng)前線程,不是的話要到 ThreadLocal 中取
        HoldCounter rh = cachedHoldCounter;
        if (rh == null || rh.tid != getThreadId(current))
            rh = readHolds.get();
        
        int count = rh.count;
        if (count <= 1) {
            
            // 這一步將 ThreadLocal remove 掉,防止內(nèi)存泄漏。因?yàn)橐呀?jīng)不再持有讀鎖了
            readHolds.remove();
            
            if (count <= 0)
                // 就是那種,lock() 一次,unlock() 好幾次的逗比
                throw unmatchedUnlockException();
        }
        // count 減 1
        --rh.count;
    }
    
    for (;;) {
        int c = getState();
        // nextc 是 state 高 16 位減 1 后的值
        int nextc = c - SHARED_UNIT;
        if (compareAndSetState(c, nextc))
            // 如果 nextc == 0,那就是 state 全部 32 位都為 0,也就是讀鎖和寫鎖都空了
            // 此時(shí)這里返回 true 的話,其實(shí)是幫助喚醒后繼節(jié)點(diǎn)中的獲取寫鎖的線程
            return nextc == 0;
    }
}

讀鎖釋放的過程還是比較簡單的,主要就是將 hold count 減 1,如果減到 0 的話,還要將 ThreadLocal 中的 remove 掉。

然后是在 for 循環(huán)中將 state 的高 16 位減 1,如果發(fā)現(xiàn)讀鎖和寫鎖都釋放光了,那么喚醒后繼的獲取寫鎖的線程。

寫鎖獲取

  1. 寫鎖是獨(dú)占鎖。
  2. 如果有讀鎖被占用,寫鎖獲取是要進(jìn)入到阻塞隊(duì)列中等待的。
// WriteLock
public void lock() {
    sync.acquire(1);
}
// AQS
public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        // 如果 tryAcquire 失敗,那么進(jìn)入到阻塞隊(duì)列等待
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

// Sync
protected final boolean tryAcquire(int acquires) {

    Thread current = Thread.currentThread();
    int c = getState();
    int w = exclusiveCount(c);
    if (c != 0) {
        
        // 看下這里返回 false 的情況:
        //   c != 0 && w == 0: 寫鎖可用,但是有線程持有讀鎖(也可能是自己持有)
        //   c != 0 && w !=0 && current != getExclusiveOwnerThread(): 其他線程持有寫鎖
        //   也就是說,只要有讀鎖或?qū)戞i被占用,這次就不能獲取到寫鎖
        if (w == 0 || current != getExclusiveOwnerThread())
            return false;
        
        if (w + exclusiveCount(acquires) > MAX_COUNT)
            throw new Error("Maximum lock count exceeded");
        
        // 這里不需要 CAS,仔細(xì)看就知道了,能到這里的,只可能是寫鎖重入,不然在上面的 if 就攔截了
        setState(c + acquires);
        return true;
    }
    
    // 如果寫鎖獲取不需要 block,那么進(jìn)行 CAS,成功就代表獲取到了寫鎖
    if (writerShouldBlock() ||
        !compareAndSetState(c, c + acquires))
        return false;
    setExclusiveOwnerThread(current);
    return true;
}

下面看一眼 writerShouldBlock() 的判定,然后你再回去看一篇寫鎖獲取過程。

static final class NonfairSync extends Sync {    // 如果是非公平模式,那么 lock 的時(shí)候就可以直接用 CAS 去搶鎖,搶不到再排隊(duì)    final boolean writerShouldBlock() {        return false; // writers can always barge    }    ...}static final class FairSync extends Sync {    final boolean writerShouldBlock() {        // 如果是公平模式,那么如果阻塞隊(duì)列有線程等待的話,就乖乖去排隊(duì)        return hasQueuedPredecessors();    }    ...}

寫鎖釋放

WriteLockpublic void unlock() {    
sync.release(1);
}
AQSpublic final boolean release(int arg) {
    // 1. 釋放鎖    
    if (tryRelease(arg)) {        
        // 2. 如果獨(dú)占鎖釋放"完全",喚醒后繼節(jié)點(diǎn)        
        Node h = head;        
        if (h != null && h.waitStatus != 0)            
        unparkSuccessor(h);        
        return true;    
        
    }    
    return false;
    
}
// Sync 
// 釋放鎖,是線程安全的,因?yàn)閷戞i是獨(dú)占鎖,具有排他性
// 實(shí)現(xiàn)很簡單,state 減 1 就是了
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);   
    // 如果 exclusiveCount(nextc) == 0,也就是說包括重入的,所有的寫鎖都釋放了,    
    // 那么返回 true,這樣會進(jìn)行喚醒后繼節(jié)點(diǎn)的操作。    
    return free;
    
}

看到這里,是不是發(fā)現(xiàn)寫鎖相對于讀鎖來說要簡單很多。

鎖降級

Doug Lea 沒有說寫鎖更高級,如果有線程持有讀鎖,那么寫鎖獲取也需要等待。

不過從源碼中也可以看出,確實(shí)會給寫鎖一些特殊照顧,如非公平模式下,為了提高吞吐量,lock 的時(shí)候會先 CAS 競爭一下,能成功就代表讀鎖獲取成功了,但是如果發(fā)現(xiàn) head.next 是獲取寫鎖的線程,就不會去做 CAS 操作。

Doug Lea 將持有寫鎖的線程,去獲取讀鎖的過程稱為鎖降級(Lock downgrading)。這樣,此線程就既持有寫鎖又持有讀鎖。

但是,鎖升級是不可以的。線程持有讀鎖的話,在沒釋放的情況下不能去獲取寫鎖,因?yàn)闀l(fā)生死鎖。

回去看下寫鎖獲取的源碼:

protected final boolean tryAcquire(int acquires) {    
    Thread current = Thread.currentThread();   
    int c = getState();  
    int w = exclusiveCount(c);  
    if (c != 0) {      
        // 看下這里返回 false 的情況:    
        //   c != 0 && w == 0: 寫鎖可用,但是有線程持有讀鎖(也可能是自己持有) 
        //   c != 0 && w !=0 && current != getExclusiveOwnerThread(): 其他線程持有寫鎖       
        //   也就是說,只要有讀鎖或?qū)戞i被占用,這次就不能獲取到寫鎖      
        if (w == 0 || current != getExclusiveOwnerThread())          
        return false;       
        ...    
    }  
        ...
}

仔細(xì)想想,如果線程 a 先獲取了讀鎖,然后獲取寫鎖,那么線程 a 就到阻塞隊(duì)列休眠了,自己把自己弄休眠了,而且可能之后就沒人去喚醒它了。

總結(jié)

14

到此這篇關(guān)于Java中的讀寫鎖ReentrantReadWriteLock源碼分析的文章就介紹到這了,更多相關(guān)Java讀寫鎖ReentrantReadWriteLock內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • SpringMVC用XML方式實(shí)現(xiàn)AOP的方法示例

    SpringMVC用XML方式實(shí)現(xiàn)AOP的方法示例

    這篇文章主要介紹了SpringMVC用XML方式實(shí)現(xiàn)AOP的方法示例,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-04-04
  • 基于Session的國際化實(shí)現(xiàn)方法

    基于Session的國際化實(shí)現(xiàn)方法

    下面小編就為大家?guī)硪黄赟ession的國際化實(shí)現(xiàn)方法。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2016-08-08
  • Mybatis?如何開啟控制臺打印sql語句

    Mybatis?如何開啟控制臺打印sql語句

    這篇文章主要介紹了Mybatis?如何開啟控制臺打印sql語句問題,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-11-11
  • Java中常用阻塞隊(duì)列的問題小結(jié)

    Java中常用阻塞隊(duì)列的問題小結(jié)

    這篇文章主要介紹了Java常用阻塞隊(duì)列問題,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2022-01-01
  • Javaweb 500 服務(wù)器內(nèi)部錯(cuò)誤的解決

    Javaweb 500 服務(wù)器內(nèi)部錯(cuò)誤的解決

    這篇文章主要介紹了Javaweb 500 服務(wù)器內(nèi)部錯(cuò)誤的解決方法,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-09-09
  • Java解析XML(4種方式)案例詳解

    Java解析XML(4種方式)案例詳解

    這篇文章主要介紹了Java解析XML(4種方式)案例詳解,本篇文章通過簡要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下
    2021-08-08
  • SpringBoot上傳文件大小受限問題的解決辦法

    SpringBoot上傳文件大小受限問題的解決辦法

    最近有一次由于項(xiàng)目升級發(fā)現(xiàn)了一個(gè)上傳方面的問題,下面這篇文章主要給大家介紹了關(guān)于SpringBoot上傳文件大小受限問題的解決辦法,文中通過實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2022-05-05
  • Spring MVC 中獲取session的幾種方法(小結(jié))

    Spring MVC 中獲取session的幾種方法(小結(jié))

    這篇文章主要介紹了Spring MVC 中獲取session的幾種方法(小結(jié)),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2017-09-09
  • mybatis動態(tài)插入list傳入List參數(shù)的實(shí)例代碼

    mybatis動態(tài)插入list傳入List參數(shù)的實(shí)例代碼

    本文通過實(shí)例代碼給大家介紹了mybatis動態(tài)插入list,Mybatis 傳入List參數(shù)的方法,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友參考下吧
    2018-04-04
  • java編譯時(shí)與運(yùn)行時(shí)概念與實(shí)例詳解

    java編譯時(shí)與運(yùn)行時(shí)概念與實(shí)例詳解

    本篇文章通過實(shí)例對 java程序編譯時(shí)與運(yùn)行時(shí)進(jìn)行了詳解,需要的朋友可以參考下
    2017-04-04

最新評論