在并發(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ě)鎖的主要特性:
- 公平性選擇:支持非公平性(默認(rèn))和公平的鎖獲取方式嗤锉,吞吐量還是非公平優(yōu)于公平寸莫;
- 重入性:支持重入,讀鎖獲取后能再次獲取档冬,寫(xiě)鎖獲取之后能夠再次獲取寫(xiě)鎖膘茎,同時(shí)也能夠獲取讀鎖;
- 鎖降級(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ě)鎖的獲取
在ReentrantReadWriteLock
的WriteLock
里面
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ò)程:
因?yàn)榇嬖阪i降級(jí)情況,如果存在寫(xiě)鎖且鎖的持有者不是當(dāng)前線程則直接返回失敗凰狞,否則繼續(xù)
-
依據(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ě)期間上鎖就行了府蔗。