ReentrantReadWriteLock讀寫鎖詳解

一、讀寫鎖簡介
現(xiàn)實中有這樣一種場景:對共享資源有讀和寫的操作,且寫操作沒有讀操作那么頻繁。在沒有寫操作的時候,多個線程同時讀一個資源沒有任何問題,所以應該允許多個線程同時讀取共享資源;但是如果一個線程想去寫這些共享資源,就不應該允許其他線程對該資源進行讀和寫的操作了。
針對這種場景,JAVA的并發(fā)包提供了讀寫鎖ReentrantReadWriteLock,它表示兩個鎖,一個是讀操作相關的鎖,稱為共享鎖;一個是寫相關的鎖,稱為排他鎖,描述如下:
線程進入讀鎖的前提條件:
沒有其他線程的寫鎖,
沒有寫請求或者有寫請求,但調(diào)用線程和持有鎖的線程是同一個。
線程進入寫鎖的前提條件:
沒有其他線程的讀鎖
沒有其他線程的寫鎖
而讀寫鎖有以下三個重要的特性:
(1)公平選擇性:支持非公平(默認)和公平的鎖獲取方式,吞吐量還是非公平優(yōu)于公平。
(2)重進入:讀鎖和寫鎖都支持線程重進入。
(3)鎖降級:遵循獲取寫鎖、獲取讀鎖再釋放寫鎖的次序,寫鎖能夠降級成為讀鎖。
二、源碼解讀
我們先來看下 ReentrantReadWriteLock 類的整體結構:
public?class?ReentrantReadWriteLock?implements?ReadWriteLock, java.io.Serializable?{
????/** 讀鎖 */
????private?final?ReentrantReadWriteLock.ReadLock readerLock;
????/** 寫鎖 */
????private?final?ReentrantReadWriteLock.WriteLock writerLock;
????final?Sync sync;
????
????/** 使用默認(非公平)的排序屬性創(chuàng)建一個新的 ReentrantReadWriteLock */
????public?ReentrantReadWriteLock()?{
????????this(false);
????}
????/** 使用給定的公平策略創(chuàng)建一個新的 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、類的繼承關系
public?class?ReentrantReadWriteLock
???????implements?ReadWriteLock, java.io.Serializable?{}說明:可以看到,ReentrantReadWriteLock實現(xiàn)了ReadWriteLock接口,ReadWriteLock接口定義了獲取讀鎖和寫鎖的規(guī)范,具體需要實現(xiàn)類去實現(xiàn);同時其還實現(xiàn)了Serializable接口,表示可以進行序列化,在源代碼中可以看到ReentrantReadWriteLock實現(xiàn)了自己的序列化邏輯。
2、類的內(nèi)部類
ReentrantReadWriteLock有五個內(nèi)部類,五個內(nèi)部類之間也是相互關聯(lián)的。內(nèi)部類的關系如下圖所示。

說明:如上圖所示,Sync繼承自AQS、NonfairSync繼承自Sync類、FairSync繼承自Sync類(通過構造函數(shù)傳入的布爾值決定要構造哪一種Sync實例);ReadLock實現(xiàn)了Lock接口、WriteLock也實現(xiàn)了Lock接口。
Sync類:
(1)類的繼承關系
abstract?static?class?Sync?extends?AbstractQueuedSynchronizer?{}說明:Sync抽象類繼承自AQS抽象類,Sync類提供了對ReentrantReadWriteLock的支持。
(2)類的內(nèi)部類
Sync類內(nèi)部存在兩個內(nèi)部類,分別為HoldCounter和ThreadLocalHoldCounter,其中HoldCounter主要與讀鎖配套使用,其中,HoldCounter源碼如下。
// 計數(shù)器
static?final?class?HoldCounter?{
????// 計數(shù)
????int?count = 0;
????// Use id, not reference, to avoid garbage retention
????// 獲取當前線程的TID屬性的值
????final?long?tid = getThreadId(Thread.currentThread());
}說明:HoldCounter主要有兩個屬性,count和tid,其中count表示某個讀線程重入的次數(shù),tid表示該線程的tid字段的值,該字段可以用來唯一標識一個線程。ThreadLocalHoldCounter的源碼如下
// 本地線程計數(shù)器
static?final?class?ThreadLocalHoldCounter
????extends?ThreadLocal<HoldCounter> {
????// 重寫初始化方法,在沒有進行set的情況下,獲取的都是該HoldCounter值
????public?HoldCounter initialValue()?{
????????return?new?HoldCounter();
????}
}說明:ThreadLocalHoldCounter重寫了ThreadLocal的initialValue方法,ThreadLocal類可以將線程與對象相關聯(lián)。在沒有進行set的情況下,get到的均是initialValue方法里面生成的那個HolderCounter對象。
(3)類的屬性
abstract?static?class?Sync?extends?AbstractQueuedSynchronizer?{
????// 版本序列號
????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;
????// 本地線程計數(shù)器
????private?transient?ThreadLocalHoldCounter readHolds;
????// 緩存的計數(shù)器
????private?transient?HoldCounter cachedHoldCounter;
????// 第一個讀線程
????private?transient?Thread firstReader = null;
????// 第一個讀線程的計數(shù)
????private?transient?int?firstReaderHoldCount;
}說明:該屬性中包括了讀鎖、寫鎖線程的最大量。本地線程計數(shù)器等。
(4)類的構造函數(shù)
// 構造函數(shù)
Sync() {
????// 本地線程計數(shù)器
????readHolds = new?ThreadLocalHoldCounter();
????// 設置AQS的狀態(tài)
????setState(getState()); // ensures visibility of readHolds
}說明:在Sync的構造函數(shù)中設置了本地線程計數(shù)器和AQS的狀態(tài)state。
3、讀寫狀態(tài)的設計
同步狀態(tài)在重入鎖的實現(xiàn)中是表示被同一個線程重復獲取的次數(shù),即一個整形變量來維護,但是之前的那個表示僅僅表示是否鎖定,而不用區(qū)分是讀鎖還是寫鎖。而讀寫鎖需要在同步狀態(tài)(一個整形變量)上維護多個讀線程和一個寫線程的狀態(tài)。
讀寫鎖對于同步狀態(tài)的實現(xiàn)是在一個整形變量上通過“按位切割使用”:將變量切割成兩部分,高16位表示讀,低16位表示寫。

假設當前同步狀態(tài)值為S,get和set的操作如下:
(1)獲取寫狀態(tài):
??? S&0x0000FFFF:將高16位全部抹去
(2)獲取讀狀態(tài):
??? S>>>16:無符號補0,右移16位
(3)寫狀態(tài)加1:
???? S+1
(4)讀狀態(tài)加1:
S+(1<<16)即S + 0x00010000
在代碼層的判斷中,如果S不等于0,當寫狀態(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)用的獨占式同步狀態(tài)的獲取與釋放,因此真實的實現(xiàn)就是Sync的 tryAcquire和 tryRelease。
寫鎖的獲取,看下tryAcquire:
protected?final?boolean?tryAcquire(int?acquires)?{
????//當前線程
????Thread current = Thread.currentThread();
????//獲取狀態(tài)
????int?c = getState();
????//寫線程數(shù)量(即獲取獨占鎖的重入數(shù))
????int?w = exclusiveCount(c);
????//當前同步狀態(tài)state != 0,說明已經(jīng)有其他線程獲取了讀鎖或寫鎖
????if?(c != 0) {
????????// 當前state不為0,此時:如果寫鎖狀態(tài)為0說明讀鎖此時被占用返回false;
????????// 如果寫鎖狀態(tài)不為0且寫鎖沒有被當前線程持有返回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)
????????//此時當前線程已持有寫鎖,現(xiàn)在是重入,所以只需要修改鎖的數(shù)量即可。
????????setState(c + acquires);
????????return?true;
????}
????//到這里說明此時c=0,讀鎖和寫鎖都沒有被獲取
????//writerShouldBlock表示是否阻塞
????if?(writerShouldBlock() ||
????????!compareAndSetState(c, c + acquires))
????????return?false;
????//設置鎖為當前線程所有
????setExclusiveOwnerThread(current);
????return?true;
}其中exclusiveCount方法表示占有寫鎖的線程數(shù)量,源碼如下:
static?int?exclusiveCount(int?c)?{ return?c & EXCLUSIVE_MASK; }說明:直接將狀態(tài)state和(2^16 - 1)做與運算,其等效于將state模上2^16。寫鎖數(shù)量由state的低十六位表示。
從源代碼可以看出,獲取寫鎖的步驟如下:
(1)首先獲取c、w。c表示當前鎖狀態(tài);w表示寫線程數(shù)量。然后判斷同步狀態(tài)state是否為0。如果state!=0,說明已經(jīng)有其他線程獲取了讀鎖或寫鎖,執(zhí)行(2);否則執(zhí)行(5)。
(2)如果鎖狀態(tài)不為零(c != 0),而寫鎖的狀態(tài)為0(w = 0),說明讀鎖此時被其他線程占用,所以當前線程不能獲取寫鎖,自然返回false。或者鎖狀態(tài)不為零,而寫鎖的狀態(tài)也不為0,但是獲取寫鎖的線程不是當前線程,則當前線程也不能獲取寫鎖。
(3)判斷當前線程獲取寫鎖是否超過最大次數(shù),若超過,拋異常,反之更新同步狀態(tài)(此時當前線程已獲取寫鎖,更新是線程安全的),返回true。
(4)如果state為0,此時讀鎖或寫鎖都沒有被獲取,判斷是否需要阻塞(公平和非公平方式實現(xiàn)不同),在非公平策略下總是不會被阻塞,在公平策略下會進行判斷(判斷同步隊列中是否有等待時間更長的線程,若存在,則需要被阻塞,否則,無需阻塞),如果不需要阻塞,則CAS更新同步狀態(tài),若CAS成功則返回true,失敗則說明鎖被別的線程搶去了,返回false。如果需要阻塞則也返回false。
(5)成功獲取寫鎖后,將當前線程設置為占有寫鎖的線程,返回true。
方法流程圖如下:

寫鎖的釋放,tryRelease方法:
protected?final?boolean?tryRelease(int?releases)?{
????//若鎖的持有者不是當前線程,拋出異常
????if?(!isHeldExclusively())
????????throw?new?IllegalMonitorStateException();
????//寫鎖的新線程數(shù)
????int?nextc = getState() - releases;
????//如果獨占模式重入數(shù)為0了,說明獨占模式被釋放
????boolean?free = exclusiveCount(nextc) == 0;
????if?(free)
????????//若寫鎖的新線程數(shù)為0,則將鎖的持有者設置為null
????????setExclusiveOwnerThread(null);
????//設置寫鎖的新線程數(shù)
????//不管獨占模式是否被釋放,更新獨占重入數(shù)
????setState(nextc);
????return?free;
}寫鎖的釋放過程還是相對而言比較簡單的:首先查看當前線程是否為寫鎖的持有者,如果不是拋出異常。然后檢查釋放后寫鎖的線程數(shù)是否為0,如果為0則表示寫鎖空閑了,釋放鎖資源將鎖的持有線程設置為null,否則釋放僅僅只是一次重入鎖而已,并不能將寫鎖的線程清空。
說明:此方法用于釋放寫鎖資源,首先會判斷該線程是否為獨占線程,若不為獨占線程,則拋出異常,否則,計算釋放資源后的寫鎖的數(shù)量,若為0,表示成功釋放,資源不將被占用,否則,表示資源還被占用。其方法流程圖如下。

5、讀鎖的獲取與釋放
類似于寫鎖,讀鎖的lock和unlock的實際實現(xiàn)對應Sync的 tryAcquireShared 和 tryReleaseShared方法。
讀鎖的獲取,看下tryAcquireShared方法
protected?final int?tryAcquireShared(int?unused) {
????// 獲取當前線程
????Thread current = Thread.currentThread();
????// 獲取狀態(tài)
????int?c = getState();
????//如果寫鎖線程數(shù) != 0 ,且獨占鎖不是當前線程則返回失敗,因為存在鎖降級
????if?(exclusiveCount(c) != 0?&&
????????getExclusiveOwnerThread() != current)
????????return?-1;
????// 讀鎖數(shù)量
????int?r = sharedCount(c);
????/*
?????* readerShouldBlock():讀鎖是否需要等待(公平鎖原則)
?????* r < MAX_COUNT:持有線程小于最大數(shù)(65535)
?????* compareAndSetState(c, c + SHARED_UNIT):設置讀取鎖狀態(tài)
?????*/
?????// 讀線程是否應該被阻塞、并且小于最大值、并且比較設置成功
????if?(!readerShouldBlock() &&
????????r < MAX_COUNT &&
????????compareAndSetState(c, c + SHARED_UNIT)) {
????????//r == 0,表示第一個讀鎖線程,第一個讀鎖firstRead是不會加入到readHolds中
????????if?(r == 0) { // 讀鎖數(shù)量為0
????????????// 設置第一個讀線程
????????????firstReader = current;
????????????// 讀線程占用的資源數(shù)為1
????????????firstReaderHoldCount = 1;
????????} else?if?(firstReader == current) { // 當前線程為第一個讀線程,表示第一個讀鎖線程重入
????????????// 占用資源數(shù)加1
????????????firstReaderHoldCount++;
????????} else?{ // 讀鎖數(shù)量不為0并且不為當前線程
????????????// 獲取計數(shù)器
????????????HoldCounter rh = cachedHoldCounter;
????????????// 計數(shù)器為空或者計數(shù)器的tid不為當前正在運行的線程的tid
????????????if?(rh == null?|| rh.tid != getThreadId(current))
????????????????// 獲取當前線程對應的計數(shù)器
????????????????cachedHoldCounter = rh = readHolds.get();
????????????else?if?(rh.count == 0) // 計數(shù)為0
????????????????//加入到readHolds中
????????????????readHolds.set(rh);
????????????//計數(shù)+1
????????????rh.count++;
????????}
????????return?1;
????}
????return?fullTryAcquireShared(current);
}?其中sharedCount方法表示占有讀鎖的線程數(shù)量,源碼如下:
static?int?sharedCount(int?c)????{ return?c >>> SHARED_SHIFT; }說明:直接將state右移16位,就可以得到讀鎖的線程數(shù)量,因為state的高16位表示讀鎖,對應的第十六位表示寫鎖數(shù)量。
讀鎖獲取鎖的過程比寫鎖稍微復雜些,首先判斷寫鎖是否為0并且當前線程不占有獨占鎖,直接返回;否則,判斷讀線程是否需要被阻塞并且讀鎖數(shù)量是否小于最大值并且比較設置狀態(tài)成功,若當前沒有讀鎖,則設置第一個讀線程firstReader和firstReaderHoldCount;若當前線程線程為第一個讀線程,則增加firstReaderHoldCount;否則,將設置當前線程對應的HoldCounter對象的值。流程圖如下。

注意:更新成功后會在firstReaderHoldCount中或readHolds(ThreadLocal類型的)的本線程副本中記錄當前線程重入數(shù)(23行至43行代碼),這是為了實現(xiàn)jdk1.6中加入的getReadHoldCount()方法的,這個方法能獲取當前線程重入共享鎖的次數(shù)(state中記錄的是多個線程的總重入次數(shù)),加入了這個方法讓代碼復雜了不少,但是其原理還是很簡單的:如果當前只有一個線程的話,還不需要動用ThreadLocal,直接往firstReaderHoldCount這個成員變量里存重入數(shù),當有第二個線程來的時候,就要動用ThreadLocal變量readHolds了,每個線程擁有自己的副本,用來保存自己的重入數(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) // 不為當前線程
????????????????return?-1;
????????} else?if?(readerShouldBlock()) { // 寫線程數(shù)量為0并且讀線程被阻塞
????????????// Make sure we're not acquiring read lock reentrantly
????????????if?(firstReader == current) { // 當前線程為第一個讀線程
????????????????// assert firstReaderHoldCount > 0;
????????????} else?{ // 當前線程不為第一個讀線程
????????????????if?(rh == null) { // 計數(shù)器不為空
????????????????????//
????????????????????rh = cachedHoldCounter;
????????????????????if?(rh == null?|| rh.tid != getThreadId(current)) { // 計數(shù)器為空或者計數(shù)器的tid不為當前正在運行的線程的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)) { // 比較并且設置成功
????????????if?(sharedCount(c) == 0) { // 讀線程數(shù)量為0
????????????????// 設置第一個讀線程
????????????????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ù)中,如果下列三個條件不滿足(讀線程是否應該被阻塞、小于最大值、比較設置成功)則會進行fullTryAcquireShared函數(shù)中,它用來保證相關操作可以成功。其邏輯與tryAcquireShared邏輯類似,不再累贅。
讀鎖的釋放,tryReleaseShared方法
protected?final boolean tryReleaseShared(int?unused) {
????// 獲取當前線程
????Thread current = Thread.currentThread();
????if?(firstReader == current) { // 當前線程為第一個讀線程
????????// assert firstReaderHoldCount > 0;
????????if?(firstReaderHoldCount == 1) // 讀線程占用的資源數(shù)為1
????????????firstReader = null;
????????else?// 減少占用的資源
????????????firstReaderHoldCount--;
????} else?{ // 當前線程不為第一個讀線程
????????// 獲取緩存的計數(shù)器
????????HoldCounter rh = cachedHoldCounter;
????????if?(rh == null?|| rh.tid != getThreadId(current)) // 計數(shù)器為空或者計數(shù)器的tid不為當前正在運行的線程的tid
????????????// 獲取當前線程對應的計數(shù)器
????????????rh = readHolds.get();
????????// 獲取計數(shù)
????????int?count = rh.count;
????????if?(count <= 1) { // 計數(shù)小于等于1
????????????// 移除
????????????readHolds.remove();
????????????if?(count <= 0) // 計數(shù)小于等于0,拋出異常
????????????????throw?unmatchedUnlockException();
????????}
????????// 減少計數(shù)
????????--rh.count;
????}
????for?(;;) { // 無限循環(huán)
????????// 獲取狀態(tài)
????????int?c = getState();
????????// 獲取狀態(tài)
????????int?nextc = c - SHARED_UNIT;
????????if?(compareAndSetState(c, nextc)) // 比較并進行設置
????????????// 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;
????}
}說明:此方法表示讀鎖線程釋放鎖。首先判斷當前線程是否為第一個讀線程firstReader,若是,則判斷第一個讀線程占有的資源數(shù)firstReaderHoldCount是否為1,若是,則設置第一個讀線程firstReader為空,否則,將第一個讀線程占有的資源數(shù)firstReaderHoldCount減1;若當前線程不是第一個讀線程,那么首先會獲取緩存計數(shù)器(上一個讀鎖線程對應的計數(shù)器 ),若計數(shù)器為空或者tid不等于當前線程的tid值,則獲取當前線程的計數(shù)器,如果計數(shù)器的計數(shù)count小于等于1,則移除當前線程對應的計數(shù)器,如果計數(shù)器的計數(shù)count小于等于0,則拋出異常,之后再減少計數(shù)即可。無論何種情況,都會進入無限循環(huán),該循環(huán)可以確保成功設置狀態(tài)state。其流程圖如下。

? ??
在讀鎖的獲取、釋放過程中,總是會有一個對象存在著,同時該對象在獲取線程獲取讀鎖是+1,釋放讀鎖時-1,該對象就是HoldCounter。
? ??
要明白HoldCounter就要先明白讀鎖。前面提過讀鎖的內(nèi)在實現(xiàn)機制就是共享鎖,對于共享鎖其實我們可以稍微的認為它不是一個鎖的概念,它更加像一個計數(shù)器的概念。一次共享鎖操作就相當于一次計數(shù)器的操作,獲取共享鎖計數(shù)器+1,釋放共享鎖計數(shù)器-1。只有當線程獲取共享鎖后才能對共享鎖進行釋放、重入操作。所以HoldCounter的作用就是當前線程持有共享鎖的數(shù)量,這個數(shù)量必須要與線程綁定在一起,否則操作其他線程鎖就會拋出異常。
先看讀鎖獲取鎖的部分:
if?(r == 0) {//r == 0,表示第一個讀鎖線程,第一個讀鎖firstRead是不會加入到readHolds中
????firstReader = current;
????firstReaderHoldCount = 1;
} else?if?(firstReader == current) {//第一個讀鎖線程重入
????firstReaderHoldCount++;
} else?{ //非firstReader計數(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++; //計數(shù)+1
}這里為什么要搞一個firstRead、firstReaderHoldCount呢?而不是直接使用else那段代碼?這是為了一個效率問題,firstReader是不會放入到readHolds中的,如果讀鎖僅有一個的情況下就會避免查找readHolds。可能就看這個代碼還不是很理解HoldCounter。我們先看firstReader、firstReaderHoldCount的定義:
private?transient?Thread firstReader = null;
private?transient?int?firstReaderHoldCount;這兩個變量比較簡單,一個表示線程,當然該線程是一個特殊的線程,一個是firstReader的重入計數(shù)。
HoldCounter的定義:
static?final?class?HoldCounter?{
????int?count = 0;
????final?long?tid = Thread.currentThread().getId();
}在HoldCounter中僅有count和tid兩個變量,其中count代表著計數(shù)器,tid是線程的id。但是如果要將一個對象和線程綁定起來僅記錄tid肯定不夠的,而且HoldCounter根本不能起到綁定對象的作用,只是記錄線程tid而已。
? ??
誠然,在java中,我們知道如果要將一個線程和對象綁定在一起只有ThreadLocal才能實現(xiàn)。所以如下:
static?final?class?ThreadLocalHoldCounter
????extends?ThreadLocal<HoldCounter> {
????public?HoldCounter initialValue()?{
????????return?new?HoldCounter();
????}
}ThreadLocalHoldCounter繼承ThreadLocal,并且重寫了initialValue方法。
? ?
故而,HoldCounter應該就是綁定線程上的一個計數(shù)器,而ThradLocalHoldCounter則是線程綁定的ThreadLocal。從上面我們可以看到ThreadLocal將HoldCounter綁定到當前線程上,同時HoldCounter也持有線程Id,這樣在釋放鎖的時候才能知道ReadWriteLock里面緩存的上一個讀取線程(cachedHoldCounter)是否是當前線程。這樣做的好處是可以減少ThreadLocal.get()的次數(shù),因為這也是一個耗時操作。需要說明的是這樣HoldCounter綁定線程id而不綁定線程對象的原因是避免HoldCounter和ThreadLocal互相綁定而GC難以釋放它們(盡管GC能夠智能的發(fā)現(xiàn)這種引用而回收它們,但是這需要一定的代價),所以其實這樣做只是為了幫助GC快速回收對象而已。
三、總結
? ?
通過上面的源碼分析,我們可以發(fā)現(xiàn)一個現(xiàn)象:
? ?
在線程持有讀鎖的情況下,該線程不能取得寫鎖(因為獲取寫鎖的時候,如果發(fā)現(xiàn)當前的讀鎖被占用,就馬上獲取失敗,不管讀鎖是不是被當前線程持有)。
? ?
在線程持有寫鎖的情況下,該線程可以繼續(xù)獲取讀鎖(獲取讀鎖時如果發(fā)現(xiàn)寫鎖被占用,只有寫鎖沒有被當前線程占用的情況才會獲取失敗)。
??
仔細想想,這個設計是合理的:因為當線程獲取讀鎖的時候,可能有其他線程同時也在持有讀鎖,因此不能把獲取讀鎖的線程“升級”為寫鎖;而對于獲得寫鎖的線程,它一定獨占了讀寫鎖,因此可以繼續(xù)讓它獲取讀鎖,當它同時獲取了寫鎖和讀鎖后,還可以先釋放寫鎖繼續(xù)持有讀鎖,這樣一個寫鎖就“降級”為了讀鎖。
綜上:
一個線程要想同時持有寫鎖和讀鎖,必須先獲取寫鎖再獲取讀鎖;寫鎖可以“降級”為讀鎖;讀鎖不能“升級”為寫鎖。
原文鏈接:cnblogs.com/xiaoxi/p/9140541.html
