4. ReentrantReadWriteLock

關(guān)于讀寫(xiě)鎖的一些理論在 十一 .Java并發(fā)工具 中已經(jīng)介紹過(guò),讀寫(xiě)鎖在同一時(shí)刻可以允許多個(gè)讀線程訪問(wèn),但是在寫(xiě)線程訪問(wèn)時(shí)涣雕,所有的讀線程和其他寫(xiě)線程均被阻塞。讀寫(xiě)鎖維護(hù)了一對(duì)鎖闭翩,一個(gè)讀鎖和一個(gè)寫(xiě)鎖挣郭,通過(guò)分離讀鎖和寫(xiě)鎖,使得并發(fā)行相比一般的排他鎖有了很大提升疗韵。

除了保證寫(xiě)操作對(duì)讀操作的可見(jiàn)性以及并發(fā)行的提升外兑障,讀寫(xiě)鎖能夠簡(jiǎn)化讀寫(xiě)交互場(chǎng)景的編程方式。假設(shè)在程序中定義一個(gè)共享的用作緩存的數(shù)據(jù)結(jié)構(gòu),它大部分時(shí)間提供讀服務(wù)(例如查詢和搜索)流译,而寫(xiě)操作占有的時(shí)間很少逞怨,但是寫(xiě)操作完成后的更新需要對(duì)后續(xù)的讀服務(wù)可見(jiàn)。

在沒(méi)有讀寫(xiě)鎖支持的時(shí)候福澡,如果需要完成上述工作就要使用Java的等待通知機(jī)制叠赦,就是當(dāng)寫(xiě)操作開(kāi)始時(shí),所有晚于寫(xiě)操作的讀線程均會(huì)進(jìn)入等待狀態(tài)革砸,只有寫(xiě)操作完成并進(jìn)行通知后除秀,所有等待的讀線程才能繼續(xù)執(zhí)行,這樣做的目的是使讀操作能夠讀取到正確的數(shù)據(jù)业岁,不會(huì)出現(xiàn)臟讀鳞仙。改用讀寫(xiě)鎖實(shí)現(xiàn)上述功能,只需要在讀操作時(shí)獲取寫(xiě)鎖笔时,寫(xiě)操作時(shí)獲取寫(xiě)鎖即可棍好。當(dāng)寫(xiě)鎖被獲取到時(shí),后續(xù)的讀寫(xiě)操作都會(huì)被阻塞允耿,寫(xiě)鎖釋放后所有操作繼續(xù)執(zhí)行借笙,相比使用等待通知機(jī)制,編程方法更加簡(jiǎn)單明了较锡。

讀寫(xiě)鎖的接口與示例

ReadWriteLock接口僅定義了讀鎖和寫(xiě)鎖的兩個(gè)方法业稼,readLock()writeLock()方法。

public interface ReadWriteLock {
    /**
     * Returns the lock used for reading.
     *
     * @return the lock used for reading
     */
    Lock readLock();

    /**
     * Returns the lock used for writing.
     *
     * @return the lock used for writing
     */
    Lock writeLock();
}

而其實(shí)現(xiàn)ReentrantReadWriteLock除了接口方法外蚂蕴,還提供了一些便于外界監(jiān)控其內(nèi)部工作的方法:

方法名稱 描述
int getReadLockCount() 返回當(dāng)前讀鎖被獲取的次數(shù)低散,該次數(shù)不等于獲取讀鎖的線程數(shù),例如骡楼,一個(gè)線程連續(xù)獲取了n次讀鎖熔号,那么占據(jù)讀鎖的線程數(shù)是1,該方法返回n
int getReadHoldCount() 返回當(dāng)前線程獲取讀鎖的次數(shù)鸟整,在Java6后新增引镊,它使用ThreadLocal保存當(dāng)前線程獲取讀鎖的次數(shù)
boolean isWriteLocked() 判斷寫(xiě)鎖是否被獲取
int getWriteHoldCount() 返回當(dāng)前寫(xiě)鎖被獲取的次數(shù)

接下來(lái),通過(guò)一個(gè)示例說(shuō)明讀寫(xiě)鎖的使用方式:

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class Cache {
    private static Map<String, Object> map = new HashMap<>();
    private static ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    private static Lock readLock = readWriteLock.readLock();
    private static Lock writeLock = readWriteLock.writeLock();
    
    public static Object get(String key) {
        readLock.lock();
        try {
            return map.get(key);
        } finally {
            readLock.unlock();
        }
    }
    
    public static Object put(String key, Object value) {
        writeLock.lock();
        try {
            return map.put(key, value);
        } finally {
            writeLock.unlock();
        }
    }
    
    public static void clear() {
        writeLock.lock();
        try {
            map.clear();
        } finally {
            writeLock.unlock();
        }
    }
}

上述示例中篮条,Cache組合一個(gè)非現(xiàn)場(chǎng)安全的HashMap作為緩存的實(shí)現(xiàn)弟头,同時(shí)使用讀寫(xiě)鎖的讀鎖和寫(xiě)鎖來(lái)保證Cache是線程安全的。在讀操作get(String key)方法中涉茧,需要獲取讀鎖赴恨,這使得并發(fā)訪問(wèn)該方法時(shí)不會(huì)被阻塞。寫(xiě)操作put(String key, Object value)方法和clear()方法伴栓,在更新HashMap時(shí)必須提前獲取寫(xiě)鎖嘱支。

實(shí)現(xiàn)分析

接下來(lái)分析ReentrantReadWriteLock的實(shí)現(xiàn)蚓胸,主要包括:讀寫(xiě)狀態(tài)的設(shè)計(jì)、寫(xiě)鎖的獲取與釋放除师、讀鎖的獲取與釋放以及鎖降級(jí)沛膳。

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

讀寫(xiě)鎖同樣依賴于自定義同步器來(lái)實(shí)現(xiàn)同步功能,而讀寫(xiě)狀態(tài)就是其同步器的同步狀態(tài)汛聚∏掳玻回想下ReentrantLock中自定義同步器的實(shí)現(xiàn),同步狀態(tài)表示鎖被一個(gè)線程重復(fù)獲取的次數(shù)倚舀,而讀寫(xiě)鎖的自定義同步器需要在同步狀態(tài)(一個(gè)整形變量)上維護(hù)多個(gè)讀線程和一個(gè)寫(xiě)線程的狀態(tài)叹哭,這使得該狀態(tài)的設(shè)計(jì)成為讀寫(xiě)鎖實(shí)現(xiàn)的關(guān)鍵。

如果在一個(gè)整形變量上維護(hù)多種狀態(tài)痕貌,就一定要“按位切割使用”這個(gè)變量风罩,讀寫(xiě)鎖將變量切為兩個(gè)部分,高16位表示讀舵稠,低16位表示寫(xiě)超升。讀寫(xiě)鎖是如何迅速確定讀和寫(xiě)各自的狀態(tài)的呢?通過(guò)位運(yùn)算哺徊。假設(shè)當(dāng)前同步狀態(tài)值為S室琢,寫(xiě)狀態(tài)等于S & 0x0000FFFF(將高16位全部抹去),讀狀態(tài)等于S >>> 16(無(wú)符號(hào)右移16位)落追。當(dāng)寫(xiě)狀態(tài)加1時(shí)盈滴,等于S+1,當(dāng)讀狀態(tài)加1時(shí)轿钠,等于S + (1<<16)巢钓,也就是S + 0x00010000

根據(jù)讀寫(xiě)狀態(tài)能得出一個(gè)推論:S不等于0時(shí)疗垛,當(dāng)寫(xiě)狀態(tài)S & 0x0000FFFF等于0時(shí)症汹,讀狀態(tài)S >>> 16大于0,即讀鎖已被獲取继谚。

> line: 253
abstract static class Sync extends AbstractQueuedSynchronizer {
    private static final long serialVersionUID = 6317671515068378041L;

    /*
     * Read vs write count extraction constants and functions.
     * Lock state is logically divided into two unsigned shorts:
     * The lower one representing the exclusive (writer) lock hold count,
     * and the upper the shared (reader) hold count.
     */

    static final int SHARED_SHIFT   = 16;
    static final int SHARED_UNIT    = (1 << SHARED_SHIFT);      //0x00010000
    static final int MAX_COUNT      = (1 << SHARED_SHIFT) - 1;
    static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;  //0x0000FFFF

    /** 返回當(dāng)前被持有的共享狀態(tài)的大小(讀) */
    static int sharedCount(int c)    { return c >>> SHARED_SHIFT; }
    /** 返回當(dāng)前被獨(dú)占的共享狀態(tài)的大姓笮摇(寫(xiě)) */
    static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }

寫(xiě)鎖的獲取與釋放

寫(xiě)鎖是一個(gè)支持可重入的排他鎖花履。如果當(dāng)前線程已經(jīng)獲取了寫(xiě)鎖,則增加寫(xiě)狀態(tài)挚赊。如果當(dāng)前線程在獲取寫(xiě)鎖時(shí)诡壁,讀取已經(jīng)被獲取或者該線程不是已經(jīng)獲取了寫(xiě)鎖的線程,則當(dāng)前線程進(jìn)入等待狀態(tài)荠割。

> line: 382
protected final boolean tryAcquire(int acquires) {
    /*
     * Walkthrough:
     * 1. 如果讀狀態(tài)不為0或者寫(xiě)狀態(tài)不為0并且持有鎖的線程不是當(dāng)前線程妹卿,那么失敗
     * 2. 如果狀態(tài)已經(jīng)到了上限旺矾,失敗(這只會(huì)在同步狀態(tài)不為0時(shí)發(fā)生)
     * 3. 否則夺克,這個(gè)線程有資格獲得鎖箕宙,或者是一個(gè)重入,或許是隊(duì)列規(guī)則允許铺纽。
     *    這樣的話柬帕,更新同步狀態(tài)并且設(shè)置持有鎖的線程
     */
    Thread current = Thread.currentThread();
    int c = getState();
    int w = exclusiveCount(c);
    if (c != 0) {
        // 注意:如果 c!=0 并且 w == 0 ,那么讀狀態(tài)一定不為0狡门,即存在讀鎖
        // 否則陷寝,判斷持有鎖的線程是否是當(dāng)前線程
        if (w == 0 || current != getExclusiveOwnerThread())
            return false;

        // 寫(xiě)鎖重入
        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;
}

> line: 682
static final class NonfairSync extends Sync {
    private static final long serialVersionUID = -8159625535654395037L;

    // 非公平鎖情況下允許線程不排隊(duì)競(jìng)爭(zhēng)
    final boolean writerShouldBlock() {
        return false; // writers can always barge
    }

> line: 700
static final class FairSync extends Sync {
    private static final long serialVersionUID = -2274990926593161451L;

    // 公平鎖與ReentrantLock一樣,需要判斷前面是否有線程先請(qǐng)求鎖
    final boolean writerShouldBlock() {
        return hasQueuedPredecessors();
    }

該方法除了重入條件之外其馏,增加了一個(gè)讀鎖是否存在的判斷凤跑。如果存在讀鎖,則寫(xiě)鎖不能被獲取叛复,原因在于:讀寫(xiě)鎖要確保寫(xiě)鎖的操作對(duì)讀鎖可見(jiàn)仔引,如果允許讀鎖在已被獲取的情況下獲取寫(xiě)鎖,那么正在運(yùn)行的其他讀線程就無(wú)法感知到當(dāng)前寫(xiě)線程的操作致扯。因此肤寝,只有等待其他線程都釋放了讀鎖,寫(xiě)鎖才能被當(dāng)前線程獲取抖僵,而寫(xiě)鎖一旦被獲取鲤看,其他讀寫(xiě)線程都被阻塞。

> line: 370
/*
 * Note that tryRelease and tryAcquire can be called by
 * Conditions. So it is possible that their arguments contain
 * both read and write holds that are all released during a
 * condition wait and re-established in tryAcquire.
 */
@ReservedStackAccess
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;
}

寫(xiě)鎖的釋放與ReentrantLock的釋放基本一致耍群,每次釋放都減少寫(xiě)狀態(tài)义桂,當(dāng)寫(xiě)狀態(tài)為0時(shí)表示寫(xiě)鎖已經(jīng)被釋放。

讀鎖的獲取與釋放

讀鎖是一個(gè)支持重進(jìn)入的共享鎖蹈垢,它能夠被多個(gè)線程同時(shí)獲取慷吊,在沒(méi)有其他寫(xiě)線程訪問(wèn)時(shí),讀鎖總會(huì)被成功獲取曹抬,而所做的也只是增加讀狀態(tài)溉瓶。如果當(dāng)前線程在獲取讀鎖時(shí),寫(xiě)鎖已被其他線程獲取谤民,則進(jìn)入等待狀態(tài)堰酿。獲取讀鎖的實(shí)現(xiàn)從Java 5到Java 6變得復(fù)雜很多,主要原因是新增了一些功能张足。讀狀態(tài)是所有線程獲取讀鎖次數(shù)的總和触创,而每個(gè)線程各自獲取讀鎖的次數(shù)只能選擇保存在ThreadLocal中,由線程自身維護(hù)为牍,這使得獲取讀鎖的實(shí)現(xiàn)變得負(fù)責(zé)哼绑。因此岩馍,這里先將獲取讀鎖的代碼做了刪除,保留了必要的部分:

> line: 453
protected final int tryAcquireShared(int unused) {
    /*
     * Walkthrough:
     * 1. 如果其他線程持有了寫(xiě)鎖抖韩,失敗
     * 2. 否則蛀恩,這個(gè)線程有資格修改同步狀態(tài),先判斷是否應(yīng)該阻塞
     *    由于隊(duì)列的FIFO規(guī)則(即公平鎖情況)帽蝶。如果不需要阻塞赦肋,
     *    嘗試通過(guò)CAS更新同步狀態(tài)獲取鎖。注意励稳,這一步不檢查
     *    重入獲取佃乘,它被推遲了以避免在更加一般的非重入情況下
     *    去檢查讀線程自己的讀狀態(tài)。
     * 3. 如果步驟2失敗了驹尼,因?yàn)闆](méi)有資格或者CAS失敗或者同步狀態(tài)
     *     已經(jīng)到達(dá)上限趣避,重試
     */
    Thread current = Thread.currentThread();
    int c = getState();
    // 如果寫(xiě)狀態(tài)不為0 并且當(dāng)前線程不是持有鎖的線程,失敗
    if (exclusiveCount(c) != 0 &&
        getExclusiveOwnerThread() != current)
        return -1;
    int r = sharedCount(c);

    // 如果不需要被阻塞并且同步狀態(tài)未達(dá)上限新翎,嘗試CAS更新
    if (!readerShouldBlock() &&
        r < MAX_COUNT &&
        compareAndSetState(c, c + SHARED_UNIT)) {
        return 1;
    }

    // 如果第二步中不符合條件程帕,循環(huán)重試
    return fullTryAcquireShared(current);
}

> line: 501
final int fullTryAcquireShared(Thread current) {
    /*
     * 獲取讀狀態(tài)的完整版本。
     * 這里的代碼和tryAcquireShared幾乎相同地啰,
     * 但是增加了CAS失敗重試和可重入的相關(guān)處理部分愁拭。
     */
    for (;;) {
        int c = getState();
        if (exclusiveCount(c) != 0) {
            if (getExclusiveOwnerThread() != current)
                return -1;
            // 否則我們已經(jīng)持有了寫(xiě)鎖;如果線程在這里被阻塞了亏吝,可能引發(fā)死鎖
        } else if (readerShouldBlock()) {
            // 可能返回 -1
        }
        if (sharedCount(c) == MAX_COUNT)
            throw new Error("Maximum lock count exceeded");
        if (compareAndSetState(c, c + SHARED_UNIT)) {
            return 1;
        }
    }
}

> line: 700
static final class FairSync extends Sync {
    private static final long serialVersionUID = -2274990926593161451L;
    // 公平鎖的實(shí)現(xiàn)依然相同岭埠,判斷前面是否有先于自己的請(qǐng)求
    final boolean readerShouldBlock() {
        return hasQueuedPredecessors();
    }
}

> line: 680
static final class NonfairSync extends Sync {
    private static final long serialVersionUID = -8159625535654395037L;
    // 判斷同步隊(duì)列的第一個(gè)節(jié)點(diǎn)是否是獨(dú)占模式
    final boolean readerShouldBlock() {
        /* 避免寫(xiě)線程陷入饑餓,如果同步隊(duì)列中有一個(gè)等待的寫(xiě)
         * 線程處于頭部蔚鸥,那么就阻塞自己惜论。
         */
        return apparentlyFirstQueuedIsExclusive();
    }
}

相信通過(guò)這個(gè)簡(jiǎn)潔的版本我們對(duì)讀鎖的獲取邏輯有了較為清晰的了解,現(xiàn)在我們將新增的ThreadLocal加入進(jìn)去止喷,分析完整的版本馆类。我們先看一下Sync類中的剩余部分:

> line: 277
/**
 * 保存每個(gè)線程的讀狀態(tài)的計(jì)數(shù)器
 * 使用ThreadLocal儲(chǔ)存,同時(shí)緩存在cachedHoldCounter中
 */
static final class HoldCounter {
    int count;          // 初始化為 0
    // 使用線程的id而不是線程引用本身弹谁,避免無(wú)用后被保留乾巧,無(wú)法被 gc
    final long tid = LockSupport.getThreadId(Thread.currentThread());
}

/**
 * ThreadLocal 子類。為反序列化機(jī)制定義
 */
static final class ThreadLocalHoldCounter
    extends ThreadLocal<HoldCounter> {
    public HoldCounter initialValue() {
        return new HoldCounter();
    }
}

/**
 * 當(dāng)前線程持有的重入讀鎖數(shù)目(hold count)预愤。只在構(gòu)造函數(shù)和readObject中
 * 被初始化沟于。當(dāng)線程的hold count 變?yōu)?時(shí)被移除。
 */
private transient ThreadLocalHoldCounter readHolds;

/**
 * 最后一個(gè)成功獲取讀鎖的線程的hold count鳖粟。對(duì)于下一個(gè)釋放讀鎖的
 * 線程是最后一個(gè)獲取讀鎖的情況使用社裆。不使用volatile因?yàn)樗皇潜? * 用作是一個(gè)優(yōu)化拙绊,并且對(duì)于緩存線程來(lái)說(shuō)是不錯(cuò)的向图。
 *
 * 它可以比線程的生命周期更長(zhǎng)泳秀, 但是通過(guò)不持有線程的引用
 * 這種方式來(lái)避免死去的線程無(wú)法被垃圾回收
 */
private transient HoldCounter cachedHoldCounter;

/**
 * firstReader 是第一個(gè)獲取了讀鎖的線程。
 * firstReaderHoldCount 是 firstReader 的 hold count.
 *
 * 更確切的說(shuō)榄攀,firstReader是最后一個(gè)將共享狀態(tài)從0改為1的線程嗜傅,
 * 并且在那以后還沒(méi)有釋放讀鎖;如果沒(méi)有這樣的線程則為null
 *
 * 不會(huì)引用垃圾存留除非線程終止時(shí)沒(méi)有釋放讀鎖檩赢,因?yàn)閠ryReleaseShared
 * 會(huì)將它設(shè)為null
 *
 * 對(duì)于跟蹤非競(jìng)爭(zhēng)的讀鎖來(lái)說(shuō)吕嘀,這很廉價(jià),所以無(wú)需擔(dān)心性能問(wèn)題
 */
private transient Thread firstReader;
private transient int firstReaderHoldCount;

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

解釋了Sync中的一些字段的作用后贞瞒,我們可以分析完整的共享狀態(tài)獲取與釋放代碼了偶房。

> line: 453
protected final int tryAcquireShared(int unused) {
    Thread current = Thread.currentThread();
    int c = getState();
    if (exclusiveCount(c) != 0 &&
        getExclusiveOwnerThread() != current)
        return -1;
    int r = sharedCount(c);
    if (!readerShouldBlock() &&
        r < MAX_COUNT &&
        compareAndSetState(c, c + SHARED_UNIT)) {
        // 如果讀狀態(tài)為0,即還沒(méi)有線程獲取過(guò)讀鎖军浆,設(shè)置firstReader
        if (r == 0) {
            firstReader = current;
            firstReaderHoldCount = 1;
        // 或者如果當(dāng)前線程是firstReader棕洋,只需要增加firstReaderHoldCount即可
        // 從此處我們可以看到使用這種方式緩存的用處以及廉價(jià)性
        } else if (firstReader == current) {
            firstReaderHoldCount++;
        // 如果不是firstReader,嘗試使用緩存乒融。
        // 如果當(dāng)前線程是第二個(gè)獲取讀鎖的線程(rh==null)或者緩存已經(jīng)被設(shè)置了
        // (rh.tid != LockSupport.getThreadId(current))掰盘,即當(dāng)前線程不是最后一個(gè)
        // 獲取讀鎖的線程,更新緩存為當(dāng)前線程赞季。
        } else {
            HoldCounter rh = cachedHoldCounter;
            if (rh == null ||
                rh.tid != LockSupport.getThreadId(current))
                cachedHoldCounter = rh = readHolds.get();
            // 當(dāng)前線程為最后一個(gè)獲取讀鎖的線程愧捕,同時(shí)在它釋放了讀鎖后沒(méi)有其他
            // 線程獲取讀鎖,此時(shí)它再次獲取讀鎖申钩,此時(shí)緩存中保存的還是它次绘,
            // 所以無(wú)需ThreadLocal初始化,直接使用緩存設(shè)置ThreadLocal即可
            else if (rh.count == 0)
                readHolds.set(rh);
            rh.count++;
        }
        return 1;
    }
    return fullTryAcquireShared(current);
}

> line: 415
protected final boolean tryReleaseShared(int unused) {
    Thread current = Thread.currentThread();
    // 如果當(dāng)前線程是firstReader
    if (firstReader == current) {
        // assert firstReaderHoldCount > 0;
        // 如果只獲取了一次讀鎖典蜕,將其置為null断盛,否則減1
        if (firstReaderHoldCount == 1)
            firstReader = null;
        else
            firstReaderHoldCount--;
    } else {
        // 依然優(yōu)先查詢緩存,如果緩存中存儲(chǔ)的不是當(dāng)前線程愉舔,
        // 調(diào)用readHolds.get()從自己維護(hù)的ThreadLocal中獲取數(shù)據(jù)钢猛。
        // 當(dāng)這是最后一次釋放讀狀態(tài),即此次能夠真正釋放讀鎖時(shí)轩缤,
        // 將readHolds移除命迈。否則,將讀狀態(tài)減1
        HoldCounter rh = cachedHoldCounter;
        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;
    }
    for (;;) {
        int c = getState();
        int nextc = c - SHARED_UNIT;
        if (compareAndSetState(c, nextc))
            return nextc == 0;
    }
}

關(guān)于讀鎖的獲取與釋放現(xiàn)在已經(jīng)完全分析完火的。

鎖降級(jí)

鎖降級(jí)指的是寫(xiě)鎖降級(jí)成為讀鎖壶愤。如果當(dāng)前線程擁有寫(xiě)鎖,然后將其釋放馏鹤,最后再獲取讀鎖征椒,這種分段完成的過(guò)程不能稱之為鎖降級(jí)。鎖降級(jí)是把持住寫(xiě)鎖湃累,再獲取讀鎖勃救,隨后釋放寫(xiě)鎖的過(guò)程碍讨。

接下來(lái)看一個(gè)鎖降級(jí)的過(guò)程。因?yàn)閿?shù)據(jù)不常變化蒙秒,所以多個(gè)線程可以并發(fā)地進(jìn)行數(shù)據(jù)處理勃黍,當(dāng)數(shù)據(jù)變更后,如果當(dāng)前線程感知到數(shù)據(jù)變化晕讲,則進(jìn)行數(shù)據(jù)的準(zhǔn)備工作覆获,同時(shí)其他處理線程被阻塞,直到當(dāng)前線程完成數(shù)據(jù)準(zhǔn)備工作瓢省。

一般形式為

r.lock();
if(!unpdate) {
    r.unlock();
    try{
        w.lock();
        //修改
        r.lock();       //核心弄息,在不釋放寫(xiě)鎖時(shí)獲取讀鎖,根據(jù)前面的分析可以做到此步
    } finally {
        w.unlock();
    }
}
try {
    //使用數(shù)據(jù)
} finally {
    r.unlock();
}

當(dāng)數(shù)據(jù)發(fā)生變化時(shí)勤婚,update變量(volatile boolean)被設(shè)置為false疑枯,此時(shí)所有訪問(wèn)此方法的線程都能感知到變化,但只有一個(gè)線程能獲取寫(xiě)鎖蛔六,其他線程會(huì)阻塞在讀鎖和寫(xiě)鎖的lock()方法上荆永。

作用是提高代碼運(yùn)行效率,此時(shí)獲取讀鎖其他線程則無(wú)法獲取寫(xiě)鎖国章,或者避免不必要的讀寫(xiě)鎖競(jìng)爭(zhēng)具钥,可以使讀線程快速執(zhí)行。

ReentrantLock不支持鎖升級(jí)液兽,目的是保證數(shù)據(jù)可見(jiàn)性骂删。如果多個(gè)線程已經(jīng)獲取了讀鎖,其中有一個(gè)線程獲取了寫(xiě)鎖四啰,那么該線程對(duì)數(shù)據(jù)的更新對(duì)其他獲取了寫(xiě)鎖的線程是不可見(jiàn)的宁玫。

讀寫(xiě)鎖的輔助方法

> line: 635
final int getReadLockCount() {
    return sharedCount(getState());
}

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

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

final int getReadHoldCount() {
    if (getReadLockCount() == 0)
        return 0;

    // 嘗試從緩存中獲取
    Thread current = Thread.currentThread();
    if (firstReader == current)
        return firstReaderHoldCount;

    HoldCounter rh = cachedHoldCounter;
    if (rh != null && rh.tid == LockSupport.getThreadId(current))
        return rh.count;

    // 緩存中不存在則從自己維護(hù)的ThreadLocal中獲取
    int count = readHolds.get().count;
    if (count == 0) readHolds.remove();
    return count;
}

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市柑晒,隨后出現(xiàn)的幾起案子欧瘪,更是在濱河造成了極大的恐慌,老刑警劉巖匙赞,帶你破解...
    沈念sama閱讀 211,884評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件佛掖,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡涌庭,警方通過(guò)查閱死者的電腦和手機(jī)芥被,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,347評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)坐榆,“玉大人拴魄,你說(shuō)我怎么就攤上這事。” “怎么了匹中?”我有些...
    開(kāi)封第一講書(shū)人閱讀 157,435評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵蚀狰,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我职员,道長(zhǎng),這世上最難降的妖魔是什么跛溉? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,509評(píng)論 1 284
  • 正文 為了忘掉前任焊切,我火速辦了婚禮,結(jié)果婚禮上芳室,老公的妹妹穿的比我還像新娘专肪。我一直安慰自己,他們只是感情好堪侯,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,611評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布嚎尤。 她就那樣靜靜地躺著,像睡著了一般伍宦。 火紅的嫁衣襯著肌膚如雪芽死。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,837評(píng)論 1 290
  • 那天次洼,我揣著相機(jī)與錄音关贵,去河邊找鬼。 笑死卖毁,一個(gè)胖子當(dāng)著我的面吹牛揖曾,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播亥啦,決...
    沈念sama閱讀 38,987評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼炭剪,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了翔脱?” 一聲冷哼從身側(cè)響起奴拦,我...
    開(kāi)封第一講書(shū)人閱讀 37,730評(píng)論 0 267
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎届吁,沒(méi)想到半個(gè)月后粱坤,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,194評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡瓷产,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,525評(píng)論 2 327
  • 正文 我和宋清朗相戀三年站玄,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片濒旦。...
    茶點(diǎn)故事閱讀 38,664評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡株旷,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情晾剖,我是刑警寧澤锉矢,帶...
    沈念sama閱讀 34,334評(píng)論 4 330
  • 正文 年R本政府宣布,位于F島的核電站齿尽,受9級(jí)特大地震影響沽损,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜循头,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,944評(píng)論 3 313
  • 文/蒙蒙 一绵估、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧卡骂,春花似錦国裳、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,764評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至浓若,卻和暖如春渺杉,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背挪钓。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,997評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工少办, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人诵原。 一個(gè)月前我還...
    沈念sama閱讀 46,389評(píng)論 2 360
  • 正文 我出身青樓英妓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親绍赛。 傳聞我的和親對(duì)象是個(gè)殘疾皇子蔓纠,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,554評(píng)論 2 349

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