7 ReentrantReadWriteLock

在并發(fā)場(chǎng)景中用于解決線程安全的問(wèn)題昨悼,我們幾乎會(huì)高頻率的使用到獨(dú)占式鎖,通常使用java提供的關(guān)鍵字synchronized或者concurrents包中實(shí)現(xiàn)了Lock接口的ReentrantLock跃洛。它們都是獨(dú)占式獲取鎖率触,也就是在同一時(shí)刻只有一個(gè)線程能夠獲取鎖。而在一些業(yè)務(wù)場(chǎng)景中汇竭,大部分只是讀數(shù)據(jù)葱蝗,寫(xiě)數(shù)據(jù)很少,如果僅僅是讀數(shù)據(jù)的話并不會(huì)影響數(shù)據(jù)正確性(出現(xiàn)臟讀)细燎,而如果在這種業(yè)務(wù)場(chǎng)景下两曼,依然使用獨(dú)占鎖的話,很顯然這將是出現(xiàn)性能瓶頸的地方玻驻。
針對(duì)這種讀多寫(xiě)少的情況悼凑,java還提供了另外一個(gè)實(shí)現(xiàn)Lock接口的ReentrantReadWriteLock(讀寫(xiě)鎖)。讀寫(xiě)所允許同一時(shí)刻被多個(gè)讀線程訪問(wèn)璧瞬,但是在寫(xiě)線程訪問(wèn)時(shí)户辫,所有的讀線程和其他的寫(xiě)線程都會(huì)被阻塞。
讀寫(xiě)鎖的主要特性:

  1. 公平性選擇:支持非公平性(默認(rèn))和公平的鎖獲取方式嗤锉,吞吐量還是非公平優(yōu)于公平寸莫;
  2. 重入性:支持重入,讀鎖獲取后能再次獲取档冬,寫(xiě)鎖獲取之后能夠再次獲取寫(xiě)鎖膘茎,同時(shí)也能夠獲取讀鎖;
  3. 鎖降級(jí):遵循獲取寫(xiě)鎖酷誓,獲取讀鎖再釋放寫(xiě)鎖的次序披坏,寫(xiě)鎖能夠降級(jí)成為讀鎖

讀寫(xiě)鎖ReentrantReadWriteLock實(shí)現(xiàn)接口ReadWriteLock,該接口維護(hù)了一對(duì)相關(guān)的鎖盐数,一個(gè)用于只讀操作棒拂,另一個(gè)用于寫(xiě)入操作。只要沒(méi)有 writer,讀取鎖可以由多個(gè) reader 線程同時(shí)保持帚屉。寫(xiě)入鎖是獨(dú)占的谜诫。

ReadWriteLock定義了兩個(gè)方法。readLock()返回用于讀操作的鎖攻旦,writeLock()返回用于寫(xiě)操作的鎖喻旷。

public interface ReadWriteLock {
    Lock readLock();
    Lock writeLock();
}

再來(lái)看看ReadWriteLock的實(shí)現(xiàn)類(lèi)ReentrantReadWriteLock

/** 內(nèi)部類(lèi)  讀鎖 */
private final ReentrantReadWriteLock.ReadLock readerLock;
/** 內(nèi)部類(lèi)  寫(xiě)鎖 */
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);
}

/** 返回用于寫(xiě)入操作的鎖 */
public ReentrantReadWriteLock.WriteLock writeLock() { return writerLock; }
/** 返回用于讀取操作的鎖 */
public ReentrantReadWriteLock.ReadLock  readLock()  { return readerLock; }

abstract static class Sync extends AbstractQueuedSynchronizer {
    /**
     * 省略其余源代碼
     */
}
public static class WriteLock implements Lock, java.io.Serializable{
    /**
     * 省略其余源代碼
     */
}

public static class ReadLock implements Lock, java.io.Serializable {
    /**
     * 省略其余源代碼
     */
}

ReentrantReadWriteLock與ReentrantLock一樣,其鎖主體依然是Sync牢屋,它的讀鎖且预、寫(xiě)鎖都是依靠Sync來(lái)實(shí)現(xiàn)的。所以ReentrantReadWriteLock實(shí)際上只有一個(gè)鎖烙无,只是在獲取讀取鎖和寫(xiě)入鎖的方式上不一樣而已锋谐,它的讀寫(xiě)鎖其實(shí)就是兩個(gè)類(lèi):ReadLock、writeLock截酷,這兩個(gè)類(lèi)都是lock實(shí)現(xiàn)涮拗。

在ReentrantLock中使用一個(gè)int類(lèi)型的state來(lái)表示同步狀態(tài),該值表示鎖被一個(gè)線程重復(fù)獲取的次數(shù)迂苛。但是讀寫(xiě)鎖ReentrantReadWriteLock內(nèi)部維護(hù)著兩個(gè)一對(duì)鎖三热,需要用一個(gè)變量維護(hù)多種狀態(tài)。所以讀寫(xiě)鎖采用“按位切割使用”的方式來(lái)維護(hù)這個(gè)變量灾部,將其切分為兩部分康铭,高16為表示讀惯退,低16為表示寫(xiě)赌髓。分割之后,讀寫(xiě)鎖是如何迅速確定讀鎖和寫(xiě)鎖的狀態(tài)呢催跪?通過(guò)為運(yùn)算锁蠕。假如當(dāng)前同步狀態(tài)為S,那么寫(xiě)狀態(tài)等于 S & 0x0000FFFF(將高16位全部抹去)懊蒸,讀狀態(tài)等于S >>> 16(無(wú)符號(hào)補(bǔ)0右移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;

    //同步狀態(tài)的低16位用來(lái)表示寫(xiě)鎖的獲取次數(shù)
    static int sharedCount(int c)    { return c >>> SHARED_SHIFT; }
    //同步狀態(tài)的高16位用來(lái)表示讀鎖被獲取的次數(shù)
    static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }

當(dāng)讀鎖已經(jīng)被讀線程獲取或者寫(xiě)鎖已經(jīng)被其他寫(xiě)線程獲取,則寫(xiě)鎖獲取失斊锿琛舌仍;否則,獲取成功并支持重入通危,增加寫(xiě)狀態(tài)铸豁。

寫(xiě)鎖

寫(xiě)鎖就是一個(gè)支持可重入的排他鎖。

寫(xiě)鎖的獲取

ReentrantReadWriteLockWriteLock里面

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

最終也走到tryAcquire(int arg)菊碟,該方法在內(nèi)部類(lèi)Sync中實(shí)現(xiàn):

    protected final boolean tryAcquire(int var1) {
        Thread var2 = Thread.currentThread();
        int var3 = this.getState();
        int var4 = exclusiveCount(var3);
        if (var3 != 0) {
            //c != 0 && var4  == 0 表示存在讀鎖
            //當(dāng)前線程不是已經(jīng)獲取寫(xiě)鎖的線程
            if (var4 != 0 && var2 == this.getExclusiveOwnerThread()) {
                if (var4 + exclusiveCount(var1) > 65535) {
                    throw new Error("Maximum lock count exceeded");
                } else {
                    this.setState(var3 + var1);
                    return true;
                }
            } else {
                return false;
            }
        } else if (!this.writerShouldBlock() && this.compareAndSetState(var3, var3 + var1)) {
            this.setExclusiveOwnerThread(var2);
            return true;
        } else {
            return false;
        }
    }

該方法和ReentrantLock的tryAcquire(int arg)大致一樣节芥,在判斷重入時(shí)增加了一項(xiàng)條件:讀鎖是否存在。因?yàn)橐_保寫(xiě)鎖的操作對(duì)讀鎖是可見(jiàn)的,如果在存在讀鎖的情況下允許獲取寫(xiě)鎖头镊,那么那些已經(jīng)獲取讀鎖的其他線程可能就無(wú)法感知當(dāng)前寫(xiě)線程的操作蚣驼。因此只有等讀鎖完全釋放后,寫(xiě)鎖才能夠被當(dāng)前線程所獲取相艇,一旦寫(xiě)鎖獲取了颖杏,所有其他讀、寫(xiě)線程均會(huì)被阻塞厂捞。

寫(xiě)鎖的釋放

獲取了寫(xiě)鎖用完了則需要釋放输玷,WriteLock提供了unlock()方法釋放寫(xiě)鎖:

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

public final boolean release(int arg) {
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }
    return false;
}

寫(xiě)鎖的釋放最終還是會(huì)調(diào)用AQS的模板方法release(int arg)方法,該方法首先調(diào)用tryRelease(int arg)方法嘗試釋放鎖靡馁,tryRelease(int arg)方法為讀寫(xiě)鎖內(nèi)部類(lèi)Sync中定義了欲鹏,如下:

protected final boolean tryRelease(int releases) {
    //釋放的線程不為鎖的持有者
    if (!isHeldExclusively())
        throw new IllegalMonitorStateException();
    int nextc = getState() - releases;
    //若寫(xiě)鎖的新線程數(shù)為0,則將鎖的持有者設(shè)置為null
    boolean free = exclusiveCount(nextc) == 0;
    if (free)
        setExclusiveOwnerThread(null);
    setState(nextc);
    return free;
}

寫(xiě)鎖釋放鎖的整個(gè)過(guò)程和獨(dú)占鎖ReentrantLock相似臭墨,每次釋放均是減少寫(xiě)狀態(tài)赔嚎,當(dāng)寫(xiě)狀態(tài)為0時(shí)表示 寫(xiě)鎖已經(jīng)完全釋放了,從而等待的其他線程可以繼續(xù)訪問(wèn)讀寫(xiě)鎖胧弛,獲取同步狀態(tài)尤误,同時(shí)此次寫(xiě)線程的修改對(duì)后續(xù)的線程可見(jiàn)。

讀鎖

讀鎖為一個(gè)可重入的共享鎖结缚,它能夠被多個(gè)線程同時(shí)持有损晤,在沒(méi)有其他寫(xiě)線程訪問(wèn)時(shí),讀鎖總是或獲取成功红竭。

讀鎖的獲取

讀鎖的獲取可以通過(guò)ReadLock的lock()方法:

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

Sync的acquireShared(int arg)定義在AQS中:

public final void acquireShared(int arg) {
    if (tryAcquireShared(arg) < 0)
        doAcquireShared(arg);
}

tryAcqurireShared(int arg)嘗試獲取讀同步狀態(tài)尤勋,該方法主要用于獲取共享式同步狀態(tài),獲取成功返回 >= 0的返回結(jié)果茵宪,否則返回 < 0 的返回結(jié)果最冰。

protected final int tryAcquireShared(int unused) {
    //當(dāng)前線程
    Thread current = Thread.currentThread();
    int c = getState();
    //exclusiveCount(c)計(jì)算寫(xiě)鎖
    //如果存在寫(xiě)鎖,且鎖的持有者不是當(dāng)前線程稀火,直接返回-1
    //存在鎖降級(jí)問(wèn)題暖哨,后續(xù)闡述
    if (exclusiveCount(c) != 0 &&
            getExclusiveOwnerThread() != current)
        return -1;
    //讀鎖
    int r = sharedCount(c);

    /*
     * readerShouldBlock():讀鎖是否需要等待(公平鎖原則)
     * r < MAX_COUNT:持有線程小于最大數(shù)(65535)
     * compareAndSetState(c, c + SHARED_UNIT):設(shè)置讀取鎖狀態(tài)
     */
    if (!readerShouldBlock() &&
            r < MAX_COUNT &&
            compareAndSetState(c, c + SHARED_UNIT)) {
        /*
         * holdCount部分后面講解
         */
        if (r == 0) {
            firstReader = current;
            firstReaderHoldCount = 1;
        } else if (firstReader == current) {
            firstReaderHoldCount++;
        } else {
            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);
}

讀鎖獲取過(guò)程:

  1. 因?yàn)榇嬖阪i降級(jí)情況,如果存在寫(xiě)鎖且鎖的持有者不是當(dāng)前線程則直接返回失敗凰狞,否則繼續(xù)

  2. 依據(jù)公平性原則篇裁,判斷讀鎖是否需要阻塞,讀鎖持有線程數(shù)小于最大值(65535)赡若,且設(shè)置鎖狀態(tài)成功达布,執(zhí)行以下代碼(對(duì)于HoldCounter下面再闡述),并返回1斩熊。如果不滿足改條件往枣,執(zhí)行fullTryAcquireShared()。

    final int fullTryAcquireShared(Thread current) {
     HoldCounter rh = null;
     for (;;) {
         int c = getState();
         //鎖降級(jí)
         if (exclusiveCount(c) != 0) {
             if (getExclusiveOwnerThread() != current)
                 return -1;
         }
         //讀鎖需要阻塞
         else if (readerShouldBlock()) {
             //列頭為當(dāng)前線程
             if (firstReader == current) {
             }
             //HoldCounter后面講解
             else {
                 if (rh == null) {
                     rh = cachedHoldCounter;
                     if (rh == null || rh.tid != getThreadId(current)) {
                         rh = readHolds.get();
                         if (rh.count == 0)
                             readHolds.remove();
                     }
                 }
                 if (rh.count == 0)
                     return -1;
             }
         }
         //讀鎖超出最大范圍
         if (sharedCount(c) == MAX_COUNT)
             throw new Error("Maximum lock count exceeded");
         //CAS設(shè)置讀鎖成功
         if (compareAndSetState(c, c + SHARED_UNIT)) {
             //如果是第1次獲取“讀取鎖”,則更新firstReader和firstReaderHoldCount
             if (sharedCount(c) == 0) {
                 firstReader = current;
                 firstReaderHoldCount = 1;
             }
             //如果想要獲取鎖的線程(current)是第1個(gè)獲取鎖(firstReader)的線程,則將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);
                 //更新線程的獲取“讀取鎖”的共享計(jì)數(shù)
                 rh.count++;
                 cachedHoldCounter = rh; // cache for release
             }
             return 1;
         }
     }
    }
    

fullTryAcquireShared(Thread current)會(huì)根據(jù)“是否需要阻塞等待”分冈,“讀取鎖的共享計(jì)數(shù)是否超過(guò)限制”等等進(jìn)行處理圾另。如果不需要阻塞等待,并且鎖的共享計(jì)數(shù)沒(méi)有超過(guò)限制雕沉,則通過(guò)CAS嘗試獲取鎖集乔,并返回1。

讀鎖的釋放

與寫(xiě)鎖相同坡椒,讀鎖也提供了unlock()釋放讀鎖:

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

unlcok()方法內(nèi)部使用Sync的releaseShared(int arg)方法扰路,該方法定義在AQS中:

public final boolean releaseShared(int arg) {
    if (tryReleaseShared(arg)) {
        doReleaseShared();
        return true;
    }
    return false;
}

調(diào)用tryReleaseShared(int arg)嘗試釋放讀鎖,該方法定義在讀寫(xiě)鎖的Sync內(nèi)部類(lèi)中:

   protected final boolean tryReleaseShared(int unused) {
    Thread current = Thread.currentThread();
    //如果想要釋放鎖的線程為第一個(gè)獲取鎖的線程
    if (firstReader == current) {
        //僅獲取了一次倔叼,則需要將firstReader 設(shè)置null汗唱,否則 firstReaderHoldCount - 1
        if (firstReaderHoldCount == 1)
            firstReader = null;
        else
            firstReaderHoldCount--;
    }
    //獲取rh對(duì)象,并更新“當(dāng)前線程獲取鎖的信息”
    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;
    }
    //CAS更新同步狀態(tài)
    for (;;) {
        int c = getState();
        int nextc = c - SHARED_UNIT;
        if (compareAndSetState(c, nextc))
            return nextc == 0;
    }
}

HoldCounter

在讀鎖獲取鎖和釋放鎖的過(guò)程中丈攒,我們一直都可以看到一個(gè)變量rh (HoldCounter )哩罪,該變量在讀鎖中扮演著非常重要的作用。

我們了解讀鎖的內(nèi)在機(jī)制其實(shí)就是一個(gè)共享鎖巡验,為了更好理解HoldCounter 际插,我們暫且認(rèn)為它不是一個(gè)鎖的概率,而相當(dāng)于一個(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ì)拋出異常。我們先看HoldCounter的定義:

    static final class HoldCounter {
        int count = 0;
        final long tid = getThreadId(Thread.currentThread());
    }

HoldCounter 定義非常簡(jiǎn)單溅呢,就是一個(gè)計(jì)數(shù)器count 和線程 id tid 兩個(gè)變量澡屡。按照這個(gè)意思我們看到HoldCounter 是需要和某給線程進(jìn)行綁定了,我們知道如果要將一個(gè)對(duì)象和線程綁定僅僅有tid是不夠的咐旧,而且從上面的代碼我們可以看到HoldCounter 僅僅只是記錄了tid驶鹉,根本起不到綁定線程的作用。那么怎么實(shí)現(xiàn)呢铣墨?答案是ThreadLocal室埋,定義如下:

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

通過(guò)上面代碼HoldCounter就可以與線程進(jìn)行綁定了。故而,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í)操作搏讶。需要說(shuō)明的是這樣HoldCounter綁定線程id而不綁定線程對(duì)象的原因是避免HoldCounter和ThreadLocal互相綁定而GC難以釋放它們(盡管GC能夠智能的發(fā)現(xiàn)這種引用而回收它們佳鳖,但是這需要一定的代價(jià)),所以其實(shí)這樣做只是為了幫助GC快速回收對(duì)象而已媒惕。

看到這里我們明白了HoldCounter作用了系吩,我們?cè)诳匆粋€(gè)獲取讀鎖的代碼段:

            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
            }

這段代碼涉及了幾個(gè)變量:firstReader 、firstReaderHoldCount妒蔚、cachedHoldCounter 淑玫。我們先理清楚這幾個(gè)變量:

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

firstReader 看名字就明白了為第一個(gè)獲取讀鎖的線程,firstReaderHoldCount為第一個(gè)獲取讀鎖的重入數(shù)面睛,cachedHoldCounter為HoldCounter的緩存絮蒿。

理清楚上面所有的變量了,HoldCounter也明白了叁鉴,我們就來(lái)給上面那段代碼標(biāo)明注釋土涝,如下:

//如果獲取讀鎖的線程為第一次獲取讀鎖的線程,則firstReaderHoldCount重入數(shù) + 1
else if (firstReader == current) {
    firstReaderHoldCount++;
} else {
    //非firstReader計(jì)數(shù)
    if (rh == null)
        rh = cachedHoldCounter;
    //rh == null 或者 rh.tid != current.getId()幌墓,需要獲取rh
    if (rh == null || rh.tid != getThreadId(current))
        rh = readHolds.get();
        //加入到readHolds中
    else if (rh.count == 0)
        readHolds.set(rh);
    //計(jì)數(shù)+1
    rh.count++;
    cachedHoldCounter = rh; // cache for release
}

這里解釋下為何要引入firstRead但壮、firstReaderHoldCount。這是為了一個(gè)效率問(wèn)題常侣,firstReader是不會(huì)放入到readHolds中的蜡饵,如果讀鎖僅有一個(gè)的情況下就會(huì)避免查找readHolds。

鎖降級(jí)

讀寫(xiě)鎖支持鎖降級(jí)胳施,遵循按照獲取寫(xiě)鎖溯祸,獲取讀鎖再釋放寫(xiě)鎖的次序,寫(xiě)鎖能夠降級(jí)成為讀鎖舞肆,不支持鎖升級(jí)焦辅,關(guān)于鎖降級(jí)下面的示例代碼摘自ReentrantWriteReadLock源碼中:

void processCachedData() {
    rwl.readLock().lock();
    if (!cacheValid) {
        // Must release read lock before acquiring write lock
        rwl.readLock().unlock();
        rwl.writeLock().lock();
        try {
            // Recheck state because another thread might have
            // acquired write lock and changed state before we did.
            if (!cacheValid) {
                data = ...
        cacheValid = true;
      }
      // Downgrade by acquiring read lock before releasing write lock
      rwl.readLock().lock();
    } finally {
      rwl.writeLock().unlock(); // Unlock write, still hold read
    }
  }

  try {
    use(data);
  } finally {
    rwl.readLock().unlock();
  }
}
}

鎖降級(jí)中讀鎖的獲取釋放為必要?肯定是必要的椿胯。試想筷登,假如當(dāng)前線程A不獲取讀鎖而是直接釋放了寫(xiě)鎖,這個(gè)時(shí)候另外一個(gè)線程B獲取了寫(xiě)鎖哩盲,那么這個(gè)線程B對(duì)數(shù)據(jù)的修改是不會(huì)對(duì)當(dāng)前線程A可見(jiàn)的前方。如果獲取了讀鎖狈醉,則線程B在獲取寫(xiě)鎖過(guò)程中判斷如果有讀鎖還沒(méi)有釋放則會(huì)被阻塞,只有當(dāng)前線程A釋放讀鎖后惠险,線程B才會(huì)獲取寫(xiě)鎖成功舔糖。

讀寫(xiě)鎖的應(yīng)用場(chǎng)景

在多線程的環(huán)境下,對(duì)同一份數(shù)據(jù)進(jìn)行讀寫(xiě)莺匠,會(huì)涉及到線程安全的問(wèn)題金吗。比如在一個(gè)線程讀取數(shù)據(jù)的時(shí)候,另外一個(gè)線程在寫(xiě)數(shù)據(jù)趣竣,而導(dǎo)致前后數(shù)據(jù)的不一致性摇庙;一個(gè)線程在寫(xiě)數(shù)據(jù)的時(shí)候,另一個(gè)線程也在寫(xiě)遥缕,同樣也會(huì)導(dǎo)致線程前后看到的數(shù)據(jù)的不一致性卫袒。
這時(shí)候可以在讀寫(xiě)方法中加入互斥鎖,任何時(shí)候只能允許一個(gè)線程的一個(gè)讀或?qū)懖僮鞯ハ唬辉试S其他線程的讀或?qū)懖僮飨δ@樣是可以解決這樣以上的問(wèn)題,但是效率卻大打折扣了户秤。因?yàn)樵谡鎸?shí)的業(yè)務(wù)場(chǎng)景中码秉,一份數(shù)據(jù),讀取數(shù)據(jù)的操作次數(shù)通常高于寫(xiě)入數(shù)據(jù)的操作鸡号,而線程與線程間的讀讀操作是不涉及到線程安全的問(wèn)題转砖,沒(méi)有必要加入互斥鎖,只要在讀-寫(xiě)鲸伴,寫(xiě)-寫(xiě)期間上鎖就行了府蔗。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市汞窗,隨后出現(xiàn)的幾起案子姓赤,更是在濱河造成了極大的恐慌,老刑警劉巖仲吏,帶你破解...
    沈念sama閱讀 221,888評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件不铆,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡蜘矢,警方通過(guò)查閱死者的電腦和手機(jī)狂男,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,677評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門(mén)综看,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)品腹,“玉大人,你說(shuō)我怎么就攤上這事红碑∥杩裕” “怎么了泡垃?”我有些...
    開(kāi)封第一講書(shū)人閱讀 168,386評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)羡鸥。 經(jīng)常有香客問(wèn)我蔑穴,道長(zhǎng),這世上最難降的妖魔是什么惧浴? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,726評(píng)論 1 297
  • 正文 為了忘掉前任存和,我火速辦了婚禮,結(jié)果婚禮上衷旅,老公的妹妹穿的比我還像新娘捐腿。我一直安慰自己,他們只是感情好柿顶,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,729評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布茄袖。 她就那樣靜靜地躺著,像睡著了一般嘁锯。 火紅的嫁衣襯著肌膚如雪宪祥。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 52,337評(píng)論 1 310
  • 那天家乘,我揣著相機(jī)與錄音蝗羊,去河邊找鬼。 笑死仁锯,一個(gè)胖子當(dāng)著我的面吹牛肘交,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播扑馁,決...
    沈念sama閱讀 40,902評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼涯呻,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了腻要?” 一聲冷哼從身側(cè)響起复罐,我...
    開(kāi)封第一講書(shū)人閱讀 39,807評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎雄家,沒(méi)想到半個(gè)月后效诅,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,349評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡趟济,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,439評(píng)論 3 340
  • 正文 我和宋清朗相戀三年乱投,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片顷编。...
    茶點(diǎn)故事閱讀 40,567評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡戚炫,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出媳纬,到底是詐尸還是另有隱情双肤,我是刑警寧澤施掏,帶...
    沈念sama閱讀 36,242評(píng)論 5 350
  • 正文 年R本政府宣布,位于F島的核電站茅糜,受9級(jí)特大地震影響七芭,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜蔑赘,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,933評(píng)論 3 334
  • 文/蒙蒙 一狸驳、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧缩赛,春花似錦锌历、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,420評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至物喷,卻和暖如春卤材,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背峦失。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,531評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工扇丛, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人尉辑。 一個(gè)月前我還...
    沈念sama閱讀 48,995評(píng)論 3 377
  • 正文 我出身青樓帆精,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親隧魄。 傳聞我的和親對(duì)象是個(gè)殘疾皇子卓练,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,585評(píng)論 2 359

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

  • ReadWriteLock 從這一節(jié)開(kāi)始介紹鎖里面的最后一個(gè)工具:讀寫(xiě)鎖(ReadWriteLock)。 Reen...
    raincoffee閱讀 649評(píng)論 0 1
  • Java8張圖 11购啄、字符串不變性 12襟企、equals()方法、hashCode()方法的區(qū)別 13狮含、...
    Miley_MOJIE閱讀 3,709評(píng)論 0 11
  • 1.ios高性能編程 (1).內(nèi)層 最小的內(nèi)層平均值和峰值(2).耗電量 高效的算法和數(shù)據(jù)結(jié)構(gòu)(3).初始化時(shí)...
    歐辰_OSR閱讀 29,417評(píng)論 8 265
  • 一 前言: 在 Xcode7.0 中新建工程 都是 默認(rèn)有個(gè) main.Storyboard 顽悼,而且默認(rèn)的根...
    LikeSomeBody閱讀 1,737評(píng)論 0 2
  • 六點(diǎn)半,對(duì)好的鬧鈴滴滴嗒嗒的不停地叫我起床几迄,隨手關(guān)了鬧鐘蔚龙,迷迷糊糊的掙扎了十五分鐘最后起床了。 之前天冷以后映胁,起床...
    愛(ài)吃包子閱讀 340評(píng)論 6 8