欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Java?多線程并發(fā)?ReentrantReadWriteLock詳情

 更新時(shí)間:2022年06月16日 14:25:22   作者:自動(dòng)化BUG制造器  
這篇文章主要介紹了Java多線程并發(fā)ReentrantReadWriteLock詳情,ReentrantReadWriteLock可重入讀寫鎖。實(shí)際使用場景中,我們需要處理的操作本質(zhì)上是讀與寫,更多相關(guān)資料,感興趣的小伙伴可以參考一下下面文章內(nèi)容

前言

ReentrantReadWriteLock ,可重入讀寫鎖。實(shí)際使用場景中,我們需要處理的操作本質(zhì)上是讀與寫。而對(duì)這兩種操作進(jìn)行同步操作的難度也是不一樣的。

一般情況下,讀操作不會(huì)造成同步安全問題,因?yàn)橹皇亲x取數(shù)據(jù)而不去修改的情況下相當(dāng)于數(shù)據(jù)是不可變的,不可變本質(zhì)上是絕對(duì)的線程安全,無需進(jìn)行任何確保線程安全的操作。

而如果在一系列操作中包含了寫操作,那么就需要考慮線程安全了。在 JMM 中,寫操作本質(zhì)上是將主內(nèi)存中的數(shù)據(jù)復(fù)制到線程的工作內(nèi)存,然后進(jìn)行更新,最后同步到主內(nèi)存。如果此時(shí)有其他線程執(zhí)行讀操作,可能會(huì)讀取到更新前到舊數(shù)據(jù),就會(huì)造成數(shù)據(jù)不一致問題。

JMM 中定義的對(duì)寫操作的執(zhí)行流程中,要先去主內(nèi)存讀取數(shù)據(jù),也就是說,一個(gè)寫操作前一定包含了一個(gè)讀操作,再算上其他的讀操作場景,可以得出結(jié)論,在實(shí)際的使用場景中,讀操作一定是多于寫操作的。

按照上面的說法,好像讀操作我們不需要進(jìn)行線程安全處理,因?yàn)樗旧砭褪蔷€程安全的,那么為什么會(huì)有讀寫鎖,尤其是讀鎖這種東西存在呢?

試想一個(gè)場景,多個(gè)線程讀取一個(gè)共享資源,其中某個(gè)或某些線程在不確定的時(shí)間點(diǎn)會(huì)進(jìn)行寫操作,那么所有線程的讀取到的數(shù)據(jù)是安全的嗎?答案是不安全,因?yàn)閷懖僮鲗懭胫鲀?nèi)存不及時(shí)的話,后續(xù)其他線程的讀操作讀取到的數(shù)據(jù)就是主內(nèi)存更新前的舊數(shù)據(jù),就會(huì)導(dǎo)致臟數(shù)據(jù)問題。也就是說,寫操作需要保證線程安全,并且是獨(dú)占鎖資源的,不能再寫操作執(zhí)行時(shí),存在其他線程去執(zhí)行讀操作。那么就需要讀鎖與寫鎖配合處理同步邏輯。

常規(guī)的保證線程安全的方法就是普通的互斥鎖,互斥鎖會(huì)被一個(gè)線程持有,對(duì)其他線程造成阻塞。如果對(duì)一段有讀操作也有寫操作的代碼使用互斥鎖的話,對(duì)于爭用這個(gè)共享數(shù)據(jù)的所有線程來說,只有一個(gè)擁有鎖的線程可以正常運(yùn)行,其他線程的邏輯即使是都是讀操作。其他線程會(huì)阻塞等待鎖資源。

讀寫鎖的優(yōu)勢就是,在上面這種情況下,確保寫操作的互斥性,并在沒有寫操作的場景下,讀操作可以讓多個(gè)線程同時(shí)獲取鎖資源。

ReadWriteLock

ReentrantReadWriteLock 是基于 AbstractQueuedSynchronizer 并實(shí)現(xiàn)了 ReadWriteLock 接口實(shí)現(xiàn)的一個(gè)鎖機(jī)制。ReadWriteLock 定義了讀寫鎖的特性:

public interface ReadWriteLock {
    /**
     * Returns the lock used for reading.
     */
    Lock readLock();
    /**
     * Returns the lock used for writing.
     */
    Lock writeLock();
}

ReadWriteLock 中定義了獲取兩種鎖的方式,一個(gè)用于獲取讀鎖、一個(gè)用于獲取寫鎖。只要沒有持有寫鎖的線程在執(zhí)行,讀鎖可以同時(shí)被多個(gè)嘗試讀操作的線程持有,而寫鎖是排他鎖。

與互斥鎖相比,讀寫鎖在訪問共享數(shù)據(jù)時(shí)允許更高級(jí)的并發(fā)特性,即每次只有一個(gè)線程可以執(zhí)行寫操作,并且在沒有寫操作時(shí)其他線程可以并發(fā)讀取共享數(shù)據(jù)。從讀操作的效率來看,如果是互斥鎖每次只能一個(gè)線程執(zhí)行讀寫操作,而讀寫鎖可以多個(gè)線程讀,寫操作時(shí)才互斥,所以讀寫鎖的執(zhí)行效率更高。

ReentrantReadWriteLock 源碼分析

前面的內(nèi)容介紹了讀寫鎖的含義和優(yōu)勢,接下來分析 Java 并發(fā)包中對(duì)它的實(shí)現(xiàn) ReentrantReadWriteLock 。

類關(guān)系

public class ReentrantReadWriteLock implements ReadWriteLock, java.io.Serializable {
    abstract static class Sync extends AbstractQueuedSynchronizer {
      	static final class HoldCounter
        static final class ThreadLocalHoldCounter extends ThreadLocal<HoldCounter> 
    }
    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
}

ReentrantReadWriteLock 實(shí)現(xiàn)了讀寫鎖接口 ReadWriteLock 和序列化接口 Serializable 。

它有一個(gè)抽象靜態(tài)內(nèi)部類 Sync ,Sync 是 AQS 的抽象子類,Sync 有兩個(gè)靜態(tài)實(shí)現(xiàn) NonfairSync 和 FairSync ,這部分是鎖邏輯的核心內(nèi)容;Sync 還有兩個(gè)內(nèi)部數(shù)據(jù)結(jié)構(gòu)類 HoldCounter 和 ThreadLocalHoldCounter 。

ReadLock 和 WriteLock 分別對(duì)應(yīng)了讀鎖和寫鎖,它們都實(shí)現(xiàn)了 Lock 接口和序列號(hào)接口 Serializable 。它們是 ReentrantReadWriteLock 中對(duì)不同操作的鎖類型的實(shí)現(xiàn),使用了裝飾模式,本質(zhì)上還是通過 Sync 的能力實(shí)現(xiàn)的。

Sync

核心邏輯是來自于 Sync 及其兩個(gè)實(shí)現(xiàn),Sync 繼承自 AbstractQueuedSynchronizer ,自身有兩個(gè)內(nèi)部類 HoldCounter 和 ThreadLocalHoldCounter 。

HoldCounter

static final class HoldCounter {
    int count;          // initially 0
    // Use id, not reference, to avoid garbage retention
    final long tid = LockSupport.getThreadId(Thread.currentThread());
}

HoldCounter 是一個(gè)計(jì)數(shù)器,count 用來記錄當(dāng)前線程擁有讀鎖的數(shù)量,即讀鎖的重入次數(shù);tid 用來記錄當(dāng)前線程唯一 ID 。

Sync 有一個(gè) cachedHoldCounter 屬性,用來做緩存效果,避免每次都通過 ThreadLocal 去讀取數(shù)據(jù)。

ThreadLocalHoldCounter

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

ThreadLocalHoldCounter 重寫了 ThreadLocal 的 initialValue() ,在 ThreadLocal 沒有進(jìn)行過 set 數(shù)據(jù)的情況下,默認(rèn)讀取到的值都來自于這個(gè)方法,也就是配合 ThreadLocal 使用,默認(rèn)值返回一個(gè)新的 HoldCounter 實(shí)例。

在 Sync 中,有一個(gè)屬性 readHolds ,它的類型是 ThreadLocalHoldCounter ,用來做當(dāng)前線程讀鎖重入計(jì)數(shù)器的 ThreadLocal 包裝,便于線程讀取自己的讀鎖重入計(jì)數(shù)器。

屬性

Sync 中定義的屬性包括:

abstract static class Sync extends AbstractQueuedSynchronizer {
		// 高16位為讀鎖,低16位為寫鎖
    static final int SHARED_SHIFT   = 16;
    // 讀鎖單位
    static final int SHARED_UNIT    = (1 << SHARED_SHIFT);  // 1 * 2^16 = 65536
    // 讀鎖最大數(shù)量
    static final int MAX_COUNT      = (1 << SHARED_SHIFT) - 1;  // 2^16 - 1
    // 寫鎖最大數(shù)量
    static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;   // 2^16 - 1 獨(dú)占標(biāo)記
    // 當(dāng)前線程讀鎖重入次數(shù)。當(dāng)持有讀鎖的線程數(shù)量下降到0時(shí)刪除。
    private transient ThreadLocalHoldCounter readHolds;
    // 緩存對(duì)象,避免每次都去從 ThreadLocal 查找。
    private transient HoldCounter cachedHoldCounter;
		// 第一個(gè)獲取讀鎖線程
    private transient Thread firstReader;
    // 第一個(gè)讀鎖線程重入讀鎖的計(jì)數(shù)
    private transient int firstReaderHoldCount;
    // ...
}

構(gòu)造方法

Sync() {
		readHolds = new ThreadLocalHoldCounter(); 
    setState(getState()); // ensures visibility of readHolds
}

Sync 初始化方法創(chuàng)建了 ThreadLocalHoldCounter 并重新設(shè)置了 State ,為什么要重新設(shè)置呢?因?yàn)檫@里要讀取當(dāng)前線程最新的同步狀態(tài)并重新設(shè)置,獲取實(shí)時(shí)的同步狀態(tài)。

核心方法

Sync 的關(guān)鍵方法包括:

abstract static class Sync extends AbstractQueuedSynchronizer {
  	// 并發(fā)計(jì)數(shù)
    static int sharedCount(int c)
    static int exclusiveCount(int c)
		// 阻塞檢查
    abstract boolean readerShouldBlock();
    abstract boolean writerShouldBlock();
		// 獲取和釋放寫鎖
    @ReservedStackAccess
    protected final boolean tryRelease(int releases)
    @ReservedStackAccess
    protected final boolean tryAcquire(int acquires)
		// 獲取和釋放讀鎖
    @ReservedStackAccess
    protected final boolean tryReleaseShared(int unused)
    @ReservedStackAccess
    protected final int tryAcquireShared(int unused)
    final int fullTryAcquireShared(Thread current)
		// 嘗試加讀寫鎖
    @ReservedStackAccess
    final boolean tryWriteLock()
    @ReservedStackAccess
    final boolean tryReadLock()

    // ... 
}

鎖的計(jì)數(shù)方法

首先是兩個(gè)靜態(tài)方法 sharedCount(int c) 和 exclusiveCount(int c) :

/** 表示共享持有的數(shù)量。 */
static int sharedCount(int c)    { return c >>> SHARED_SHIFT; } // 無符號(hào)右移,高位補(bǔ) 0 
/** 表示獨(dú)占持有的數(shù)量。 */
static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }

參數(shù) c 是 AQS 中的 state,根據(jù) state 進(jìn)行位運(yùn)算。這兩個(gè)方法可以根據(jù)鎖自身的狀態(tài)解析出持有讀寫鎖的數(shù)量。

  • sharedCount ,表示占有讀鎖的線程數(shù)量。直接將 AQS 中的 state 右移 16 位,高位補(bǔ) 0,就可以得到讀鎖的線程數(shù)量,因?yàn)?state 的高十六位表示讀鎖,對(duì)應(yīng)的低十六位表示寫鎖數(shù)量。
  • exclusiveCount,表示占有寫鎖的線程數(shù)量。直接將 AQS 的 state 和 (2^16 - 1) 做與運(yùn)算,其等效于將 state 模上 2^16 。寫鎖數(shù)量由 state 的低十六位表示。

讀寫鎖阻塞檢查方法

第二組方法是 readerShouldBlock 和 writerShouldBlock ,用來檢查當(dāng)前的讀鎖/寫鎖是否會(huì)造成當(dāng)前線程阻塞。

// 獲取和釋放對(duì)公平鎖和非公平鎖使用相同的代碼,不同點(diǎn)在于但在隊(duì)列非空時(shí)是否/如何允許碰撞。

// 如果當(dāng)前線程在嘗試獲取讀鎖時(shí),并且在其他符合條件的線程也在嘗試獲取讀鎖,由于策略其他等待線程占用了讀鎖,當(dāng)前線程應(yīng)該阻塞,則返回true。
abstract boolean readerShouldBlock();

// 如果當(dāng)前線程在嘗試獲取寫鎖時(shí),并且在其他符合條件的線程也在嘗試獲取寫鎖,由于策略其他等待線程占用了寫鎖,當(dāng)前線程應(yīng)該阻塞,則返回true。
abstract boolean writerShouldBlock();

這兩個(gè)方法的實(shí)現(xiàn)在 Sync 的子類中 -- 公平策略實(shí)現(xiàn) FairSync 和非公平策略實(shí)現(xiàn) NonfairSync。

公平策略實(shí)現(xiàn) FairSync 和非公平策略實(shí)現(xiàn) NonfairSync

    // 非公平策略
		static final class NonfairSync extends Sync {
        private static final long serialVersionUID = -8159625535654395037L;
        final boolean writerShouldBlock() {
            return false; // 正在持有寫鎖的線程永不阻塞
        }
        final boolean readerShouldBlock() {
            return apparentlyFirstQueuedIsExclusive(); 
        }
    }
		// 公平策略
    static final class FairSync extends Sync {
        private static final long serialVersionUID = -2274990926593161451L;
      
        final boolean writerShouldBlock() {
            return hasQueuedPredecessors();
        }
        final boolean readerShouldBlock() {
            return hasQueuedPredecessors();
        }
    }

公平鎖策略和非公平鎖策略的實(shí)現(xiàn),本質(zhì)上的不同是這兩個(gè)方法的實(shí)現(xiàn)。

NonfairSync 非公平策略

NonfairSync 中,執(zhí)行寫操作的線程是否應(yīng)該進(jìn)入阻塞狀態(tài)的判斷,直接是 false ,這是因?yàn)榉枪讲呗韵?,如果?dāng)前自身已經(jīng)擁有了寫鎖,直接重入,以獨(dú)占的方式繼續(xù)運(yùn)行(所以是不公平的)。

執(zhí)行讀操作的線程是否會(huì)阻塞,是通過 apparentlyFirstQueuedIsExclusive() 判斷的,這個(gè)方法是 AQS 中的方法:

    final boolean apparentlyFirstQueuedIsExclusive() {
        Node h = head, s = head.next;
        return h != null && s != null && !(s instanceof SharedNode) && s.waiter != null;
    }

這個(gè)方法的作用是,CLH 隊(duì)列中的頭節(jié)點(diǎn)和它的的 next 都存在的情況下,如果 next 節(jié)點(diǎn)不是 SharedNode ,且它的關(guān)聯(lián)線程不為空的情況(即下一個(gè)鎖不是共享鎖,共享鎖在讀寫鎖里就是讀鎖)的情況,會(huì)導(dǎo)致當(dāng)前執(zhí)行讀操作的線程進(jìn)入阻塞狀態(tài),確保寫操作的互斥特性。

FairSync 公平策略

FairSync 中,讀寫執(zhí)行線程是否應(yīng)該進(jìn)入阻塞狀態(tài)都是根據(jù) hasQueuedPredecessors() 方法判斷的:

    public final boolean hasQueuedPredecessors() {
        Thread first = null; Node h = head, s = h.next;
        if (h != null && (s == null || (first = s.waiter) == null || s.prev == null))
            first = getFirstQueuedThread(); // retry via getFirstQueuedThread
        return first != null && first != Thread.currentThread();
    }

    public final Thread getFirstQueuedThread() {
        Thread first = null, w; Node h, s;
        if ((h = head) != null && ((s = h.next) == null || (first = s.waiter) == null || s.prev == null)) {
            // traverse from tail on stale reads
            for (Node p = tail, q; p != null && (q = p.prev) != null; p = q)
                if ((w = p.waiter) != null)
                    first = w;
        }
        return first;
    }

hasQueuedPredecessors() 對(duì) head 節(jié)點(diǎn)和它的 next 節(jié)點(diǎn)進(jìn)行空檢查,并檢查下一個(gè)節(jié)點(diǎn)的執(zhí)行線程和 prev 指針是否有值,滿足條件的情況下通過 getFirstQueuedThread() 方法獲取到隊(duì)列中第一個(gè)節(jié)點(diǎn)關(guān)聯(lián)的線程。最終返回的結(jié)過是檢查這個(gè)線程不等于當(dāng)前線程。

如果存在等待隊(duì)列第一個(gè)等待執(zhí)行的線程,那么就優(yōu)先執(zhí)行這個(gè)線程。也就是說,不管當(dāng)前線程是擁有讀鎖還是寫鎖,都優(yōu)先執(zhí)行等待隊(duì)列第一個(gè)未執(zhí)行節(jié)點(diǎn),這里就能體現(xiàn)出公平,即優(yōu)先執(zhí)行等待隊(duì)列中頭一個(gè)等待的節(jié)點(diǎn)所關(guān)聯(lián)的線程。

Release 和 Acquire 方法組

這一組方法是整個(gè) Sync 的核心邏輯,也是加解鎖核心邏輯。

tryRelease

@ReservedStackAccess
protected final boolean tryRelease(int releases) {
		if (!isHeldExclusively()) // 不是獨(dú)占持有鎖的情況,直接拋出異常。
				throw new IllegalMonitorStateException();
		int nextc = getState() - releases; // AQS 當(dāng)前鎖狀態(tài) - releases = 新的鎖狀態(tài)
		boolean free = exclusiveCount(nextc) == 0; // 根據(jù)新的鎖狀態(tài)獲取到獨(dú)占寫鎖的數(shù)量 == 0
		if (free) 
				setExclusiveOwnerThread(null); // 持有寫鎖的線程數(shù)為0,更新當(dāng)前獨(dú)占線程引用
		setState(nextc); 	// 無論是不是解鎖了,都要更新鎖狀態(tài)
		return free;			// 最后返回鎖是否已經(jīng)可用了
}

tryRelease(int releases) 用來嘗試釋放寫鎖。

它的邏輯如下圖:

tryAcquire

@ReservedStackAccess
protected final boolean tryAcquire(int acquires) {
            /*
             * 工作流程:
             * 1. 如果寫鎖計(jì)數(shù)非零或所有者是不同的線程,則失敗。
             * 2. 如果寫鎖計(jì)數(shù)超過最大數(shù)量,失?。ㄟ@只發(fā)生在計(jì)數(shù)非 0 的情況)。
             * 3. 否則,如果這個(gè)線程是可重入的獲取方式或者隊(duì)列策略允許的話,它就有資格獲得鎖。
             *    如果是,更新狀態(tài)并設(shè)置 owner。
             */
		Thread current = Thread.currentThread(); // 當(dāng)前線程
		int c = getState();											 // 當(dāng)前鎖狀態(tài)
		int w = exclusiveCount(c);							 // 計(jì)算擁有寫鎖的線程數(shù)量
		if (c != 0) { // 0 是鎖可用狀態(tài),當(dāng)前狀態(tài)表面鎖狀態(tài)為被持有。
				if (w == 0 || current != getExclusiveOwnerThread()) // 對(duì)應(yīng) 【1】 的情況,寫線程數(shù)量為0或者當(dāng)前線程沒有占有獨(dú)占資源
						return false;
        if (w + exclusiveCount(acquires) > MAX_COUNT) // 對(duì)應(yīng)【2】的情況, 判斷是否超過最高寫線程數(shù)量
            throw new Error("Maximum lock count exceeded");
        // 重入獲取寫鎖
        setState(c + acquires);
        return true; 
		}
		if (writerShouldBlock() || !compareAndSetState(c, c + acquires)) // 是否應(yīng)該阻塞或更新狀態(tài)是否成功,失敗直接 return false;
				return false;
		setExclusiveOwnerThread(current); // 設(shè)置當(dāng)前為持有鎖的線程。
		return true;
}

此函數(shù)用于獲取寫鎖,首先會(huì)獲取 state ,判斷 state 是否為0。

若為0,表示此時(shí)沒有讀鎖線程,再判斷寫線程是否應(yīng)該被阻塞,而在非公平策略下總是不會(huì)被阻塞,在公平策略下會(huì)進(jìn)行判斷(判斷同步隊(duì)列中是否有等待時(shí)間更長的線程,若存在,則需要被阻塞,否則,無需阻塞),之后在設(shè)置狀態(tài)state,然后返回true。若state不為0,則表示此時(shí)存在讀鎖或?qū)戞i線程,若寫鎖線程數(shù)量為0或者當(dāng)前線程為獨(dú)占鎖線程,則返回false,表示不成功,否則,判斷寫鎖線程的重入次數(shù)是否大于了最大值,若是,則拋出異常,否則,設(shè)置狀態(tài)state,返回true,表示成功。

其函數(shù)流程圖如下:

tryReleaseShared:

        @ReservedStackAccess
        protected final boolean tryReleaseShared(int unused) {
            Thread current = Thread.currentThread(); // 當(dāng)前線程
            if (firstReader == current) { // 當(dāng)前線程是否是第一個(gè)讀線程
                // assert firstReaderHoldCount > 0;
                if (firstReaderHoldCount == 1) 
                    firstReader = null;  // 釋放線程引用
                else
                    firstReaderHoldCount--;  // 當(dāng)前線程重入次數(shù)自減
            } else {
                HoldCounter rh = cachedHoldCounter; // 獲取當(dāng)前線程的重入讀鎖的次數(shù)
                if (rh == null || rh.tid != LockSupport.getThreadId(current))
                    rh = readHolds.get();
                int count = rh.count;
                if (count <= 1) {
                    readHolds.remove();
                    if (count <= 0)
                        throw unmatchedUnlockException();
                }
                --rh.count;
            }
          	// 死循環(huán)直到更新狀態(tài)成功
            for (;;) {
                int c = getState();
                int nextc = c - SHARED_UNIT;
                if (compareAndSetState(c, nextc))
                    // 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;
            }
        }

tryAcquireShared :

        @ReservedStackAccess
        protected final int tryAcquireShared(int unused) {
            /*
             * Walkthrough:
             * 1. If write lock held by another thread, fail.
             * 2. Otherwise, this thread is eligible for
             *    lock wrt state, so ask if it should block
             *    because of queue policy. If not, try
             *    to grant by CASing state and updating count.
             *    Note that step does not check for reentrant
             *    acquires, which is postponed to full version
             *    to avoid having to check hold count in
             *    the more typical non-reentrant case.
             * 3. If step 2 fails either because thread
             *    apparently not eligible or CAS fails or count
             *    saturated, chain to version with full retry loop.
             */
            Thread current = Thread.currentThread();
            int c = getState();
            if (exclusiveCount(c) != 0 && getExclusiveOwnerThread() != current) // 當(dāng)獨(dú)占線程不是當(dāng)前線程
                return -1;
            int r = sharedCount(c); // 共享讀鎖的線程數(shù)量
          	// 檢查讀線程不應(yīng)該阻塞 and 持有讀鎖的線程數(shù)量小于 MAX_COUNT and 更新鎖狀態(tài)成功
            if (!readerShouldBlock() && r < MAX_COUNT && compareAndSetState(c, c + SHARED_UNIT)) {
                if (r == 0) {  // 第一個(gè)嘗試獲取讀鎖的線程
                    firstReader = current;
                    firstReaderHoldCount = 1;
                } else if (firstReader == current) {  // 第一個(gè)線程重入
                    firstReaderHoldCount++;
                } else {	
                    HoldCounter rh = cachedHoldCounter;
                  	// 無緩存 or 當(dāng)前線程不是計(jì)數(shù)器所在線程
                    if (rh == null || rh.tid != LockSupport.getThreadId(current)) 
                        cachedHoldCounter = rh = readHolds.get(); // 從 ThreadLocal 中讀取
                    else if (rh.count == 0) 
                        readHolds.set(rh);
                    rh.count++; // 當(dāng)前線程獲取讀鎖次數(shù) + 1
                }
                return 1;
            }
            return fullTryAcquireShared(current);
        }

最后執(zhí)行到了 fullTryAcquireShared :

        final int fullTryAcquireShared(Thread current) {
            /*
             * 這段代碼與 tryAcquireShared 中的部分代碼是冗余的,但總體上更簡單,因?yàn)樗粫?huì)使
             * tryAcquireShared 在重試和懶加載讀鎖計(jì)數(shù)之間的交互復(fù)雜化。
             */
            HoldCounter rh = null;
            for (;;) { // 死循環(huán),不斷嘗試
                int c = getState();
                if (exclusiveCount(c) != 0) { // 獨(dú)占檢查是否是當(dāng)前線程
                    if (getExclusiveOwnerThread() != current)
                        return -1;
                // 否則我們持有獨(dú)占鎖;這里的阻塞將導(dǎo)致死鎖。
                } else if (readerShouldBlock()) {
                    // 確保我們不是重入式地獲取讀鎖
                    if (firstReader == current) {
                        // assert firstReaderHoldCount > 0;
                    } else {
                      	// 不是重入的情況下,更新 HoldCounter
                        if (rh == null) { 
                            rh = cachedHoldCounter;
                            if (rh == null || rh.tid != LockSupport.getThreadId(current)) {
                                rh = readHolds.get();
                                if (rh.count == 0)
                                    readHolds.remove();
                            }
                        }
                        if (rh.count == 0)
                            return -1;
                    }
                }
              	// 共享讀鎖 == 最大數(shù)量,拋出異常
                if (sharedCount(c) == MAX_COUNT) throw new Error("Maximum lock count exceeded");
              	// 是否能夠設(shè)置成功
                if (compareAndSetState(c, c + SHARED_UNIT)) {
                    if (sharedCount(c) == 0) { // 第一個(gè)線程
                        firstReader = current;
                        firstReaderHoldCount = 1;
                    } else if (firstReader == current) { // 重入
                        firstReaderHoldCount++;
                    } else { // 其他情況
                        if (rh == null)
                            rh = cachedHoldCounter;
                        if (rh == null || rh.tid != LockSupport.getThreadId(current))
                            rh = readHolds.get();
                        else if (rh.count == 0)
                            readHolds.set(rh);
                        rh.count++;
                        cachedHoldCounter = rh; // cache for release
                    }
                    return 1;
                }
            }
        }

這個(gè)方法的整體邏輯與 tryAcquireShared 基本相同。

ReadLock

public static class ReadLock implements Lock, java.io.Serializable {
    private static final long serialVersionUID = -5992448646407690164L;
    private final Sync sync;
    protected ReadLock(ReentrantReadWriteLock lock) {
        sync = lock.sync;
    }
    public void lock() {
        sync.acquireShared(1);
    }
    public void lockInterruptibly() throws InterruptedException {
        sync.acquireSharedInterruptibly(1);
    }
    public boolean tryLock() {
        return sync.tryReadLock();
    }
    public boolean tryLock(long timeout, TimeUnit unit)
        throws InterruptedException {
        return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
    }
    public void unlock() {
        sync.releaseShared(1);
    }
    public Condition newCondition() {
        throw new UnsupportedOperationException();
    }
    // ... 
}

ReadLock 實(shí)現(xiàn)了 Lock 接口,代理調(diào)用到邏輯都是 Sync 中 Shared 組的核心方法。ReadLock 可以通過 readLock(): ReadLock 方法獲取到。

還有一點(diǎn)值得注意,newCondition() 方法直接拋出了異常,這是因?yàn)樽x鎖是一種共享鎖,不會(huì)導(dǎo)致互斥,所以也就不支持使用 Condition 控制阻塞與喚醒。

WriteLock

public static class WriteLock implements Lock, java.io.Serializable {
    private static final long serialVersionUID = -4992448646407690164L;
    private final Sync sync;
    protected WriteLock(ReentrantReadWriteLock lock) {
        sync = lock.sync;
    }
    public void lock() {
        sync.acquire(1);
    }
    public void lockInterruptibly() throws InterruptedException {
        sync.acquireInterruptibly(1);
    }
    public boolean tryLock() {
        return sync.tryWriteLock();
    }
    public boolean tryLock(long timeout, TimeUnit unit)
            throws InterruptedException {
        return sync.tryAcquireNanos(1, unit.toNanos(timeout));
    }
    public void unlock() {
        sync.release(1);
    }
    public Condition newCondition() {
        return sync.newCondition();
    }
    public String toString() {
        Thread o = sync.getOwner();
        return super.toString() + ((o == null) ? "[Unlocked]" : "[Locked by thread " + o.getName() + "]");
    }
   public boolean isHeldByCurrentThread() {
        return sync.isHeldExclusively();
    }
    public int getHoldCount() {
        return sync.getWriteHoldCount();
    }
}

寫鎖本質(zhì)上也是代理 Sync 中的核心方法。

讀寫鎖降級(jí)

鎖降級(jí)指的是寫鎖降級(jí)為讀鎖,如果當(dāng)前線程擁有寫鎖,將其釋放然后再獲取讀鎖,這種操作過程不是鎖降級(jí)。鎖降級(jí)是指把線程當(dāng)前持有寫鎖,再去獲取讀鎖,隨后釋放寫鎖,這個(gè)流程稱為鎖降級(jí)。

public void processData() {
    readLock.lock();
    if (!update) {
        // 必須先釋放讀鎖
        readLock.unlock();
        // 鎖降級(jí)從寫鎖獲取到開始
        writeLock.lock();
        try {
            if (!update) {
                // 準(zhǔn)備數(shù)據(jù)的流程(略)
                update = true;
            }
            readLock.lock();
        } finally {
            writeLock.unlock();
        }
        // 鎖降級(jí)完成,寫鎖降級(jí)為讀鎖
    }
    try {
        // 使用數(shù)據(jù)的流程(略)
    } finally {
        readLock.unlock();
    }
}

鎖降級(jí)可以保證數(shù)據(jù)的可見性,如果再持有寫鎖的情況下,不先去獲取讀鎖,直接釋放寫鎖,再嘗試獲取讀鎖,這一系列操作中會(huì)有短暫的無鎖狀態(tài),此時(shí)如果有其他線程獲取了寫鎖并修改數(shù)據(jù),那么當(dāng)前線程就無法感知到數(shù)據(jù)更新,如果當(dāng)前線程先獲取了讀鎖,那么其他線程就會(huì)阻塞,直到當(dāng)前線程釋放讀鎖后才能獲取寫鎖進(jìn)行更新。

讀寫鎖 ReentrantReadWriteLock 不支持鎖升級(jí),目的是保證數(shù)據(jù)的可見性,如果讀鎖已被多個(gè)線程獲取,其中任意線程成功獲取了寫鎖,并更新了數(shù)據(jù),那么這個(gè)更新對(duì)其他線程是不可見的,容易造成數(shù)據(jù)不一致問題。

總結(jié)

  • ReentrantReadWriteLock 底層加解鎖原理是 AQS
  • ReentrantReadWriteLock 分為 ReadLock 和 WriteLock 兩種鎖,ReadLock 是共享鎖,WriteLock 是互斥鎖。
  • ReentrantReadWriteLock 的寫鎖可重入是根據(jù) AQS 中的 state 計(jì)數(shù)的;讀鎖的可重入是 Sync 中的 HoldCounter 來記錄的。
  • 公平策略和非公平策略都需要對(duì)讀鎖和寫鎖分別實(shí)現(xiàn)一個(gè)判斷邏輯。
  • 核心實(shí)現(xiàn)在 Sync 方法中。

到此這篇關(guān)于Java 多線程并發(fā) ReentrantReadWriteLock詳情的文章就介紹到這了,更多相關(guān)Java ReentrantReadWriteLock內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • java中l(wèi)ong(Long)與int(Integer)之間的轉(zhuǎn)換方式

    java中l(wèi)ong(Long)與int(Integer)之間的轉(zhuǎn)換方式

    這篇文章主要介紹了java中l(wèi)ong(Long)與int(Integer)之間的轉(zhuǎn)換方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧
    2020-10-10
  • Java基礎(chǔ)教程之字符流文件讀寫

    Java基礎(chǔ)教程之字符流文件讀寫

    這篇文章主要給大家介紹了關(guān)于Java基礎(chǔ)教程之字符流文件讀寫的相關(guān)資料,,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2018-07-07
  • JavaWeb Session失效時(shí)間設(shè)置方法

    JavaWeb Session失效時(shí)間設(shè)置方法

    這篇文章主要介紹了JavaWeb Session失效時(shí)間設(shè)置方法,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下
    2016-12-12
  • Java基于迭代器模式實(shí)現(xiàn)的訪問人員列表操作示例

    Java基于迭代器模式實(shí)現(xiàn)的訪問人員列表操作示例

    這篇文章主要介紹了Java基于迭代器模式實(shí)現(xiàn)的訪問人員列表操作,簡單描述了迭代器模式的概念、原理以及使用迭代器模式實(shí)現(xiàn)訪問人員列表的相關(guān)操作技巧,需要的朋友可以參考下
    2018-05-05
  • 如何解決SpringBoot集成百度UEditor圖片上傳后直接訪問404

    如何解決SpringBoot集成百度UEditor圖片上傳后直接訪問404

    在本篇文章里小編給大家整理的是一篇關(guān)于如何解決SpringBoot集成百度UEditor圖片上傳后直接訪問404相關(guān)文章,需要的朋友們學(xué)習(xí)下。
    2019-11-11
  • Java事務(wù)的個(gè)人理解小結(jié)

    Java事務(wù)的個(gè)人理解小結(jié)

    數(shù)據(jù)庫操作的事務(wù)習(xí)慣上就稱為Java事務(wù)
    2013-03-03
  • 在Java編程中定義方法

    在Java編程中定義方法

    這篇文章主要介紹了在Java編程中定義方法,是Java入門學(xué)習(xí)中的基礎(chǔ)知識(shí),需要的朋友可以參考下
    2015-10-10
  • RocketMQ設(shè)計(jì)之故障規(guī)避機(jī)制

    RocketMQ設(shè)計(jì)之故障規(guī)避機(jī)制

    這篇文章主要介紹了RocketMQ設(shè)計(jì)之故障規(guī)避機(jī)制,故障規(guī)避機(jī)制就是用來解決當(dāng)Broker出現(xiàn)故障,Producer不能及時(shí)感知而導(dǎo)致消息發(fā)送失敗的問題,下面詳細(xì)介紹需要的小伙伴可以參考一下
    2022-03-03
  • SpringBoot詳細(xì)講解日志文件

    SpringBoot詳細(xì)講解日志文件

    Spring Boot默認(rèn)使用SLF4J+Logback 記錄日志,并提供了默認(rèn)配置,即使我們不進(jìn)行任何額外配,也可以使用SLF4J+Logback進(jìn)行日志輸出
    2022-06-06
  • 深入理解java線程通信

    深入理解java線程通信

    開發(fā)中不免會(huì)遇到需要所有子線程執(zhí)行完畢通知主線程處理某些邏輯的場景。或者是線程 A 在執(zhí)行到某個(gè)條件通知線程 B 執(zhí)行某個(gè)操作。下面我們來一起學(xué)習(xí)如何解決吧
    2019-05-05

最新評(píng)論