關(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;
}