一央渣、讀寫鎖簡(jiǎn)介
現(xiàn)實(shí)中有這樣一種場(chǎng)景:對(duì)共享資源有讀和寫的操作计盒,且寫操作沒有讀操作那么頻繁。在沒有寫操作的時(shí)候芽丹,多個(gè)線程同時(shí)讀一個(gè)資源沒有任何問題北启,所以應(yīng)該允許多個(gè)線程同時(shí)讀取共享資源;但是如果一個(gè)線程想去寫這些共享資源拔第,就不應(yīng)該允許其他線程對(duì)該資源進(jìn)行讀和寫的操作了咕村。
針對(duì)這種場(chǎng)景,JAVA的并發(fā)包提供了讀寫鎖ReentrantReadWriteLock蚊俺,它表示兩個(gè)鎖懈涛,一個(gè)是讀操作相關(guān)的鎖,稱為共享鎖泳猬;一個(gè)是寫相關(guān)的鎖批钠,稱為排他鎖宇植,描述如下:
線程進(jìn)入讀鎖的前提條件:
沒有其他線程的寫鎖,
沒有寫請(qǐng)求或者有寫請(qǐng)求埋心,但調(diào)用線程和持有鎖的線程是同一個(gè)指郁。
線程進(jìn)入寫鎖的前提條件:
沒有其他線程的讀鎖
沒有其他線程的寫鎖
而讀寫鎖有以下三個(gè)重要的特性:
(1)公平選擇性:支持非公平(默認(rèn))和公平的鎖獲取方式,吞吐量還是非公平優(yōu)于公平拷呆。
(2)重進(jìn)入:讀鎖和寫鎖都支持線程重進(jìn)入坡氯。
(3)鎖降級(jí):遵循獲取寫鎖、獲取讀鎖再釋放寫鎖的次序洋腮,寫鎖能夠降級(jí)成為讀鎖箫柳。
二、源碼解讀
我們先來看下 ReentrantReadWriteLock 類的整體結(jié)構(gòu):
/** 讀鎖 */
private final ReentrantReadWriteLock.ReadLock readerLock;
/** 寫鎖 */
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);
}
/** 返回用于寫入操作的鎖 */
public ReentrantReadWriteLock.WriteLock writeLock() { return writerLock; }
/** 返回用于讀取操作的鎖 */
public ReentrantReadWriteLock.ReadLock readLock() { return readerLock; }
abstract static class Sync extends AbstractQueuedSynchronizer {}
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 {}
}
1啥供、類的繼承關(guān)系
public class ReentrantReadWriteLock
implements ReadWriteLock, java.io.Serializable {}
說明:可以看到悯恍,ReentrantReadWriteLock實(shí)現(xiàn)了ReadWriteLock接口,ReadWriteLock接口定義了獲取讀鎖和寫鎖的規(guī)范伙狐,具體需要實(shí)現(xiàn)類去實(shí)現(xiàn)涮毫;同時(shí)其還實(shí)現(xiàn)了Serializable接口,表示可以進(jìn)行序列化贷屎,在源代碼中可以看到ReentrantReadWriteLock實(shí)現(xiàn)了自己的序列化邏輯罢防。
2、類的內(nèi)部類
ReentrantReadWriteLock有五個(gè)內(nèi)部類唉侄,五個(gè)內(nèi)部類之間也是相互關(guān)聯(lián)的咒吐。內(nèi)部類的關(guān)系如下圖所示。說明:如上圖所示属划,Sync繼承自AQS恬叹、NonfairSync繼承自Sync類、FairSync繼承自Sync類(通過構(gòu)造函數(shù)傳入的布爾值決定要構(gòu)造哪一種Sync實(shí)例)同眯;ReadLock實(shí)現(xiàn)了Lock接口绽昼、WriteLock也實(shí)現(xiàn)了Lock接口。
Sync類:
(1)類的繼承關(guān)系
abstract static class Sync extends AbstractQueuedSynchronizer {}
說明:Sync抽象類繼承自AQS抽象類须蜗,Sync類提供了對(duì)ReentrantReadWriteLock的支持硅确。
(2)類的內(nèi)部類
Sync類內(nèi)部存在兩個(gè)內(nèi)部類,分別為HoldCounter和ThreadLocalHoldCounter明肮,其中HoldCounter主要與讀鎖配套使用菱农,其中,HoldCounter源碼如下晤愧。
// 計(jì)數(shù)器
static final class HoldCounter {
// 計(jì)數(shù)
int count = 0;
// Use id, not reference, to avoid garbage retention
// 獲取當(dāng)前線程的TID屬性的值
final long tid = getThreadId(Thread.currentThread());
}
說明:HoldCounter主要有兩個(gè)屬性大莫,count和tid蛉腌,其中count表示某個(gè)讀線程重入的次數(shù)官份,tid表示該線程的tid字段的值只厘,該字段可以用來唯一標(biāo)識(shí)一個(gè)線程。ThreadLocalHoldCounter的源碼如下
// 本地線程計(jì)數(shù)器
static final class ThreadLocalHoldCounter
extends ThreadLocal<HoldCounter> {
// 重寫初始化方法舅巷,在沒有進(jìn)行set的情況下羔味,獲取的都是該HoldCounter值
public HoldCounter initialValue() {
return new HoldCounter();
}
}
說明:ThreadLocalHoldCounter重寫了ThreadLocal的initialValue方法,ThreadLocal類可以將線程與對(duì)象相關(guān)聯(lián)钠右。在沒有進(jìn)行set的情況下赋元,get到的均是initialValue方法里面生成的那個(gè)HolderCounter對(duì)象。
(3)類的屬性
abstract static class Sync extends AbstractQueuedSynchronizer {
// 版本序列號(hào)
private static final long serialVersionUID = 6317671515068378041L;
// 高16位為讀鎖飒房,低16位為寫鎖
static final int SHARED_SHIFT = 16;
// 讀鎖單位
static final int SHARED_UNIT = (1 << SHARED_SHIFT);
// 讀鎖最大數(shù)量
static final int MAX_COUNT = (1 << SHARED_SHIFT) - 1;
// 寫鎖最大數(shù)量
static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;
// 本地線程計(jì)數(shù)器
private transient ThreadLocalHoldCounter readHolds;
// 緩存的計(jì)數(shù)器
private transient HoldCounter cachedHoldCounter;
// 第一個(gè)讀線程
private transient Thread firstReader = null;
// 第一個(gè)讀線程的計(jì)數(shù)
private transient int firstReaderHoldCount;
}
說明:該屬性中包括了讀鎖搁凸、寫鎖線程的最大量。本地線程計(jì)數(shù)器等狠毯。
(4)類的構(gòu)造函數(shù)
// 構(gòu)造函數(shù)
Sync() {
// 本地線程計(jì)數(shù)器
readHolds = new ThreadLocalHoldCounter();
// 設(shè)置AQS的狀態(tài)
setState(getState()); // ensures visibility of readHolds
}
說明:在Sync的構(gòu)造函數(shù)中設(shè)置了本地線程計(jì)數(shù)器和AQS的狀態(tài)state护糖。
3、讀寫狀態(tài)的設(shè)計(jì)
同步狀態(tài)在重入鎖的實(shí)現(xiàn)中是表示被同一個(gè)線程重復(fù)獲取的次數(shù)嚼松,即一個(gè)整形變量來維護(hù)嫡良,但是之前的那個(gè)表示僅僅表示是否鎖定,而不用區(qū)分是讀鎖還是寫鎖献酗。而讀寫鎖需要在同步狀態(tài)(一個(gè)整形變量)上維護(hù)多個(gè)讀線程和一個(gè)寫線程的狀態(tài)寝受。
讀寫鎖對(duì)于同步狀態(tài)的實(shí)現(xiàn)是在一個(gè)整形變量上通過“按位切割使用”:將變量切割成兩部分,高16位表示讀罕偎,低16位表示寫很澄。
假設(shè)當(dāng)前同步狀態(tài)值為S,get和set的操作如下:
(1)獲取寫狀態(tài):
S&0x0000FFFF:將高16位全部抹去
(2)獲取讀狀態(tài):
S>>>16:無符號(hào)補(bǔ)0颜及,右移16位
(3)寫狀態(tài)加1:
S+1
(4)讀狀態(tài)加1:
S+(1<<16)即S + 0x00010000
在代碼層的判斷中痴怨,如果S不等于0,當(dāng)寫狀態(tài)(S&0x0000FFFF)器予,而讀狀態(tài)(S>>>16)大于0浪藻,則表示該讀寫鎖的讀鎖已被獲取。
4乾翔、寫鎖的獲取與釋放
看下WriteLock類中的lock和unlock方法:
public void lock() {
sync.acquire(1);
}
public void unlock() {
sync.release(1);
}
可以看到就是調(diào)用的獨(dú)占式同步狀態(tài)的獲取與釋放爱葵,因此真實(shí)的實(shí)現(xiàn)就是Sync的 tryAcquire和 tryRelease。
寫鎖的獲取反浓,看下tryAcquire:
protected final boolean tryAcquire(int acquires) {
//當(dāng)前線程
Thread current = Thread.currentThread();
//獲取狀態(tài)
int c = getState();
//寫線程數(shù)量(即獲取獨(dú)占鎖的重入數(shù))
int w = exclusiveCount(c);
//當(dāng)前同步狀態(tài)state != 0萌丈,說明已經(jīng)有其他線程獲取了讀鎖或?qū)戞i
if (c != 0) {
// 當(dāng)前state不為0,此時(shí):如果寫鎖狀態(tài)為0說明讀鎖此時(shí)被占用返回false雷则;
// 如果寫鎖狀態(tài)不為0且寫鎖沒有被當(dāng)前線程持有返回false
if (w == 0 || current != getExclusiveOwnerThread())
return false;
//判斷同一線程獲取寫鎖是否超過最大次數(shù)(65535)辆雾,支持可重入
if (w + exclusiveCount(acquires) > MAX_COUNT)
throw new Error("Maximum lock count exceeded");
//更新狀態(tài)
//此時(shí)當(dāng)前線程已持有寫鎖,現(xiàn)在是重入月劈,所以只需要修改鎖的數(shù)量即可度迂。
setState(c + acquires);
return true;
}
//到這里說明此時(shí)c=0,讀鎖和寫鎖都沒有被獲取
//writerShouldBlock表示是否阻塞
if (writerShouldBlock() ||
!compareAndSetState(c, c + acquires))
return false;
//設(shè)置鎖為當(dāng)前線程所有
setExclusiveOwnerThread(current);
return true;
}
其中exclusiveCount方法表示占有寫鎖的線程數(shù)量藤乙,源碼如下:
static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }
說明:直接將狀態(tài)state和(2^16 - 1)做與運(yùn)算,其等效于將state模上2^16惭墓。寫鎖數(shù)量由state的低十六位表示坛梁。
從源代碼可以看出,獲取寫鎖的步驟如下:
(1)首先獲取c腊凶、w划咐。c表示當(dāng)前鎖狀態(tài);w表示寫線程數(shù)量钧萍。然后判斷同步狀態(tài)state是否為0褐缠。如果state!=0,說明已經(jīng)有其他線程獲取了讀鎖或?qū)戞i风瘦,執(zhí)行(2)送丰;否則執(zhí)行(5)。
(2)如果鎖狀態(tài)不為零(c != 0)弛秋,而寫鎖的狀態(tài)為0(w = 0)器躏,說明讀鎖此時(shí)被其他線程占用,所以當(dāng)前線程不能獲取寫鎖蟹略,自然返回false登失。或者鎖狀態(tài)不為零挖炬,而寫鎖的狀態(tài)也不為0揽浙,但是獲取寫鎖的線程不是當(dāng)前線程,則當(dāng)前線程也不能獲取寫鎖意敛。
(3)判斷當(dāng)前線程獲取寫鎖是否超過最大次數(shù)馅巷,若超過,拋異常草姻,反之更新同步狀態(tài)(此時(shí)當(dāng)前線程已獲取寫鎖钓猬,更新是線程安全的),返回true撩独。
(4)如果state為0敞曹,此時(shí)讀鎖或?qū)戞i都沒有被獲取,判斷是否需要阻塞(公平和非公平方式實(shí)現(xiàn)不同)综膀,在非公平策略下總是不會(huì)被阻塞澳迫,在公平策略下會(huì)進(jìn)行判斷(判斷同步隊(duì)列中是否有等待時(shí)間更長(zhǎng)的線程,若存在剧劝,則需要被阻塞橄登,否則,無需阻塞),如果不需要阻塞拢锹,則CAS更新同步狀態(tài)谣妻,若CAS成功則返回true,失敗則說明鎖被別的線程搶去了面褐,返回false。如果需要阻塞則也返回false取胎。
(5)成功獲取寫鎖后展哭,將當(dāng)前線程設(shè)置為占有寫鎖的線程,返回true闻蛀。
方法流程圖如下:
寫鎖的釋放匪傍,tryRelease方法:
//若鎖的持有者不是當(dāng)前線程,拋出異常
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
//寫鎖的新線程數(shù)
int nextc = getState() - releases;
//如果獨(dú)占模式重入數(shù)為0了觉痛,說明獨(dú)占模式被釋放
boolean free = exclusiveCount(nextc) == 0;
if (free)
//若寫鎖的新線程數(shù)為0役衡,則將鎖的持有者設(shè)置為null
setExclusiveOwnerThread(null);
//設(shè)置寫鎖的新線程數(shù)
//不管獨(dú)占模式是否被釋放,更新獨(dú)占重入數(shù)
setState(nextc);
return free;
}
寫鎖的釋放過程還是相對(duì)而言比較簡(jiǎn)單的:首先查看當(dāng)前線程是否為寫鎖的持有者薪棒,如果不是拋出異常手蝎。然后檢查釋放后寫鎖的線程數(shù)是否為0,如果為0則表示寫鎖空閑了俐芯,釋放鎖資源將鎖的持有線程設(shè)置為null棵介,否則釋放僅僅只是一次重入鎖而已,并不能將寫鎖的線程清空吧史。
說明:此方法用于釋放寫鎖資源邮辽,首先會(huì)判斷該線程是否為獨(dú)占線程,若不為獨(dú)占線程贸营,則拋出異常吨述,否則,計(jì)算釋放資源后的寫鎖的數(shù)量钞脂,若為0揣云,表示成功釋放,資源不將被占用冰啃,否則灵再,表示資源還被占用。其方法流程圖如下亿笤。
5翎迁、讀鎖的獲取與釋放
類似于寫鎖,讀鎖的lock和unlock的實(shí)際實(shí)現(xiàn)對(duì)應(yīng)Sync的 tryAcquireShared 和 tryReleaseShared方法净薛。
讀鎖的獲取汪榔,看下tryAcquireShared方法
protected final int tryAcquireShared(int unused) {
// 獲取當(dāng)前線程
Thread current = Thread.currentThread();
// 獲取狀態(tài)
int c = getState();
//如果寫鎖線程數(shù) != 0 ,且獨(dú)占鎖不是當(dāng)前線程則返回失敗,因?yàn)榇嬖阪i降級(jí)
if (exclusiveCount(c) != 0 &&
getExclusiveOwnerThread() != current)
return -1;
// 讀鎖數(shù)量
int r = sharedCount(c);
/*
* readerShouldBlock():讀鎖是否需要等待(公平鎖原則)
* r < MAX_COUNT:持有線程小于最大數(shù)(65535)
* compareAndSetState(c, c + SHARED_UNIT):設(shè)置讀取鎖狀態(tài)
*/
// 讀線程是否應(yīng)該被阻塞痴腌、并且小于最大值雌团、并且比較設(shè)置成功
if (!readerShouldBlock() &&
r < MAX_COUNT &&
compareAndSetState(c, c + SHARED_UNIT)) {
//r == 0,表示第一個(gè)讀鎖線程士聪,第一個(gè)讀鎖firstRead是不會(huì)加入到readHolds中
if (r == 0) { // 讀鎖數(shù)量為0
// 設(shè)置第一個(gè)讀線程
firstReader = current;
// 讀線程占用的資源數(shù)為1
firstReaderHoldCount = 1;
} else if (firstReader == current) { // 當(dāng)前線程為第一個(gè)讀線程锦援,表示第一個(gè)讀鎖線程重入
// 占用資源數(shù)加1
firstReaderHoldCount++;
} else { // 讀鎖數(shù)量不為0并且不為當(dāng)前線程
// 獲取計(jì)數(shù)器
HoldCounter rh = cachedHoldCounter;
// 計(jì)數(shù)器為空或者計(jì)數(shù)器的tid不為當(dāng)前正在運(yùn)行的線程的tid
if (rh == null || rh.tid != getThreadId(current))
// 獲取當(dāng)前線程對(duì)應(yīng)的計(jì)數(shù)器
cachedHoldCounter = rh = readHolds.get();
else if (rh.count == 0) // 計(jì)數(shù)為0
//加入到readHolds中
readHolds.set(rh);
//計(jì)數(shù)+1
rh.count++;
}
return 1;
}
return fullTryAcquireShared(current);
}
其中sharedCount方法表示占有讀鎖的線程數(shù)量,源碼如下:
<pre style="margin: 0px; padding: 0px; white-space: pre-wrap; overflow-wrap: break-word; font-family: "Courier New" !important; font-size: 12px !important;">static int sharedCount(int c) { return c >>> SHARED_SHIFT; }</pre>
說明:直接將state右移16位剥悟,就可以得到讀鎖的線程數(shù)量灵寺,因?yàn)閟tate的高16位表示讀鎖,對(duì)應(yīng)的第十六位表示寫鎖數(shù)量区岗。
讀鎖獲取鎖的過程比寫鎖稍微復(fù)雜些略板,首先判斷寫鎖是否為0并且當(dāng)前線程不占有獨(dú)占鎖,直接返回慈缔;否則叮称,判斷讀線程是否需要被阻塞并且讀鎖數(shù)量是否小于最大值并且比較設(shè)置狀態(tài)成功,若當(dāng)前沒有讀鎖藐鹤,則設(shè)置第一個(gè)讀線程firstReader和firstReaderHoldCount瓤檐;若當(dāng)前線程線程為第一個(gè)讀線程,則增加firstReaderHoldCount娱节;否則距帅,將設(shè)置當(dāng)前線程對(duì)應(yīng)的HoldCounter對(duì)象的值。流程圖如下括堤。
注意:更新成功后會(huì)在firstReaderHoldCount中或readHolds(ThreadLocal類型的)的本線程副本中記錄當(dāng)前線程重入數(shù)(23行至43行代碼)碌秸,這是為了實(shí)現(xiàn)jdk1.6中加入的getReadHoldCount()方法的,這個(gè)方法能獲取當(dāng)前線程重入共享鎖的次數(shù)(state中記錄的是多個(gè)線程的總重入次數(shù))悄窃,加入了這個(gè)方法讓代碼復(fù)雜了不少讥电,但是其原理還是很簡(jiǎn)單的:如果當(dāng)前只有一個(gè)線程的話,還不需要?jiǎng)佑肨hreadLocal轧抗,直接往firstReaderHoldCount這個(gè)成員變量里存重入數(shù)恩敌,當(dāng)有第二個(gè)線程來的時(shí)候,就要?jiǎng)佑肨hreadLocal變量readHolds了横媚,每個(gè)線程擁有自己的副本纠炮,用來保存自己的重入數(shù)。
fullTryAcquireShared方法:
final int fullTryAcquireShared(Thread current) {
HoldCounter rh = null;
for (;;) { // 無限循環(huán)
// 獲取狀態(tài)
int c = getState();
if (exclusiveCount(c) != 0) { // 寫線程數(shù)量不為0
if (getExclusiveOwnerThread() != current) // 不為當(dāng)前線程
return -1;
} else if (readerShouldBlock()) { // 寫線程數(shù)量為0并且讀線程被阻塞
// Make sure we're not acquiring read lock reentrantly
if (firstReader == current) { // 當(dāng)前線程為第一個(gè)讀線程
// assert firstReaderHoldCount > 0;
} else { // 當(dāng)前線程不為第一個(gè)讀線程
if (rh == null) { // 計(jì)數(shù)器不為空
//
rh = cachedHoldCounter;
if (rh == null || rh.tid != getThreadId(current)) { // 計(jì)數(shù)器為空或者計(jì)數(shù)器的tid不為當(dāng)前正在運(yùn)行的線程的tid
rh = readHolds.get();
if (rh.count == 0)
readHolds.remove();
}
}
if (rh.count == 0)
return -1;
}
}
if (sharedCount(c) == MAX_COUNT) // 讀鎖數(shù)量為最大值灯蝴,拋出異常
throw new Error("Maximum lock count exceeded");
if (compareAndSetState(c, c + SHARED_UNIT)) { // 比較并且設(shè)置成功
if (sharedCount(c) == 0) { // 讀線程數(shù)量為0
// 設(shè)置第一個(gè)讀線程
firstReader = current;
//
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);
rh.count++;
cachedHoldCounter = rh; // cache for release
}
return 1;
}
}
}
說明:在tryAcquireShared函數(shù)中恢口,如果下列三個(gè)條件不滿足(讀線程是否應(yīng)該被阻塞、小于最大值穷躁、比較設(shè)置成功)則會(huì)進(jìn)行fullTryAcquireShared函數(shù)中耕肩,它用來保證相關(guān)操作可以成功。其邏輯與tryAcquireShared邏輯類似,不再累贅猿诸。
讀鎖的釋放婚被,tryReleaseShared方法
protected final boolean tryReleaseShared(int unused) {
// 獲取當(dāng)前線程
Thread current = Thread.currentThread();
if (firstReader == current) { // 當(dāng)前線程為第一個(gè)讀線程
// assert firstReaderHoldCount > 0;
if (firstReaderHoldCount == 1) // 讀線程占用的資源數(shù)為1
firstReader = null;
else // 減少占用的資源
firstReaderHoldCount--;
} else { // 當(dāng)前線程不為第一個(gè)讀線程
// 獲取緩存的計(jì)數(shù)器
HoldCounter rh = cachedHoldCounter;
if (rh == null || rh.tid != getThreadId(current)) // 計(jì)數(shù)器為空或者計(jì)數(shù)器的tid不為當(dāng)前正在運(yùn)行的線程的tid
// 獲取當(dāng)前線程對(duì)應(yīng)的計(jì)數(shù)器
rh = readHolds.get();
// 獲取計(jì)數(shù)
int count = rh.count;
if (count <= 1) { // 計(jì)數(shù)小于等于1
// 移除
readHolds.remove();
if (count <= 0) // 計(jì)數(shù)小于等于0,拋出異常
throw unmatchedUnlockException();
}
// 減少計(jì)數(shù)
--rh.count;
}
for (;;) { // 無限循環(huán)
// 獲取狀態(tài)
int c = getState();
// 獲取狀態(tài)
int nextc = c - SHARED_UNIT;
if (compareAndSetState(c, nextc)) // 比較并進(jìn)行設(shè)置
// 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;
}
}
說明:此方法表示讀鎖線程釋放鎖梳虽。首先判斷當(dāng)前線程是否為第一個(gè)讀線程firstReader址芯,若是,則判斷第一個(gè)讀線程占有的資源數(shù)firstReaderHoldCount是否為1窜觉,若是谷炸,則設(shè)置第一個(gè)讀線程firstReader為空,否則竖螃,將第一個(gè)讀線程占有的資源數(shù)firstReaderHoldCount減1淑廊;若當(dāng)前線程不是第一個(gè)讀線程逗余,那么首先會(huì)獲取緩存計(jì)數(shù)器(上一個(gè)讀鎖線程對(duì)應(yīng)的計(jì)數(shù)器 )特咆,若計(jì)數(shù)器為空或者tid不等于當(dāng)前線程的tid值,則獲取當(dāng)前線程的計(jì)數(shù)器录粱,如果計(jì)數(shù)器的計(jì)數(shù)count小于等于1腻格,則移除當(dāng)前線程對(duì)應(yīng)的計(jì)數(shù)器,如果計(jì)數(shù)器的計(jì)數(shù)count小于等于0啥繁,則拋出異常菜职,之后再減少計(jì)數(shù)即可。無論何種情況旗闽,都會(huì)進(jìn)入無限循環(huán)酬核,該循環(huán)可以確保成功設(shè)置狀態(tài)state。其流程圖如下适室。
在讀鎖的獲取嫡意、釋放過程中,總是會(huì)有一個(gè)對(duì)象存在著捣辆,同時(shí)該對(duì)象在獲取線程獲取讀鎖是+1蔬螟,釋放讀鎖時(shí)-1,該對(duì)象就是HoldCounter汽畴。
要明白HoldCounter就要先明白讀鎖旧巾。前面提過讀鎖的內(nèi)在實(shí)現(xiàn)機(jī)制就是共享鎖,對(duì)于共享鎖其實(shí)我們可以稍微的認(rèn)為它不是一個(gè)鎖的概念忍些,它更加像一個(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ì)拋出異常。
先看讀鎖獲取鎖的部分:
if (r == 0) {//r == 0是尔,表示第一個(gè)讀鎖線程殉了,第一個(gè)讀鎖firstRead是不會(huì)加入到readHolds中
firstReader = current;
firstReaderHoldCount = 1;
} else if (firstReader == current) {//第一個(gè)讀鎖線程重入
firstReaderHoldCount++;
} else { //非firstReader計(jì)數(shù)
HoldCounter rh = cachedHoldCounter;//readHoldCounter緩存
//rh == null 或者 rh.tid != current.getId(),需要獲取rh
if (rh == null || rh.tid != current.getId())
cachedHoldCounter = rh = readHolds.get();
else if (rh.count == 0)
readHolds.set(rh); //加入到readHolds中
rh.count++; //計(jì)數(shù)+1
}
這里為什么要搞一個(gè)firstRead拟枚、firstReaderHoldCount呢薪铜?而不是直接使用else那段代碼?這是為了一個(gè)效率問題恩溅,firstReader是不會(huì)放入到readHolds中的隔箍,如果讀鎖僅有一個(gè)的情況下就會(huì)避免查找readHolds〗畔纾可能就看這個(gè)代碼還不是很理解HoldCounter蜒滩。我們先看firstReader、firstReaderHoldCount的定義:
private transient Thread firstReader = null;
private transient int firstReaderHoldCount;
這兩個(gè)變量比較簡(jiǎn)單奶稠,一個(gè)表示線程嘶炭,當(dāng)然該線程是一個(gè)特殊的線程得运,一個(gè)是firstReader的重入計(jì)數(shù)。
HoldCounter的定義:
static final class HoldCounter {
int count = 0;
final long tid = Thread.currentThread().getId();
}
在HoldCounter中僅有count和tid兩個(gè)變量,其中count代表著計(jì)數(shù)器信殊,tid是線程的id诺苹。但是如果要將一個(gè)對(duì)象和線程綁定起來僅記錄tid肯定不夠的抛姑,而且HoldCounter根本不能起到綁定對(duì)象的作用卓嫂,只是記錄線程tid而已。
誠(chéng)然蜈项,在java中芹关,我們知道如果要將一個(gè)線程和對(duì)象綁定在一起只有ThreadLocal才能實(shí)現(xiàn)。所以如下:
static final class ThreadLocalHoldCounter
extends ThreadLocal<HoldCounter> {
public HoldCounter initialValue() {
return new HoldCounter();
}
}
ThreadLocalHoldCounter繼承ThreadLocal战得,并且重寫了initialValue方法充边。
故而,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í)操作坡倔。需要說明的是這樣HoldCounter綁定線程id而不綁定線程對(duì)象的原因是避免HoldCounter和ThreadLocal互相綁定而GC難以釋放它們(盡管GC能夠智能的發(fā)現(xiàn)這種引用而回收它們漂佩,但是這需要一定的代價(jià))脖含,所以其實(shí)這樣做只是為了幫助GC快速回收對(duì)象而已。
三投蝉、總結(jié)
通過上面的源碼分析养葵,我們可以發(fā)現(xiàn)一個(gè)現(xiàn)象:
在線程持有讀鎖的情況下,該線程不能取得寫鎖(因?yàn)楂@取寫鎖的時(shí)候瘩缆,如果發(fā)現(xiàn)當(dāng)前的讀鎖被占用关拒,就馬上獲取失敗,不管讀鎖是不是被當(dāng)前線程持有)庸娱。
在線程持有寫鎖的情況下着绊,該線程可以繼續(xù)獲取讀鎖(獲取讀鎖時(shí)如果發(fā)現(xiàn)寫鎖被占用,只有寫鎖沒有被當(dāng)前線程占用的情況才會(huì)獲取失斒煳尽)归露。
仔細(xì)想想,這個(gè)設(shè)計(jì)是合理的:因?yàn)楫?dāng)線程獲取讀鎖的時(shí)候斤儿,可能有其他線程同時(shí)也在持有讀鎖剧包,因此不能把獲取讀鎖的線程“升級(jí)”為寫鎖;而對(duì)于獲得寫鎖的線程雇毫,它一定獨(dú)占了讀寫鎖玄捕,因此可以繼續(xù)讓它獲取讀鎖踩蔚,當(dāng)它同時(shí)獲取了寫鎖和讀鎖后棚放,還可以先釋放寫鎖繼續(xù)持有讀鎖,這樣一個(gè)寫鎖就“降級(jí)”為了讀鎖馅闽。
綜上:
一個(gè)線程要想同時(shí)持有寫鎖和讀鎖飘蚯,必須先獲取寫鎖再獲取讀鎖;寫鎖可以“降級(jí)”為讀鎖福也;讀鎖不能“升級(jí)”為寫鎖局骤。
謝謝你的閱讀,如果您覺得這篇博文對(duì)你有幫助暴凑,請(qǐng)點(diǎn)贊或者喜歡峦甩,讓更多的人看到!祝你每天開心愉快现喳!