本文內(nèi)容:讀寫鎖 ReentrantReadWriteLock 的源碼分析硼啤,基于 Java7/Java8。
閱讀建議:雖然我這里會介紹一些 AQS 的知識斧账,不過如果你完全不了解 AQS,看本文就有點吃力了煞肾。
1咧织、使用示例
下面這個例子非常實用,我是 javadoc 的搬運工:
// 這是一個關(guān)于緩存操作的故事
classCachedData{
Object data;
volatile boolean cacheValid;
// 讀寫鎖實例
final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock;
voidprocessCachedData{
// 獲取讀鎖
rwl.readLock.lock;
if (!cacheValid) { // 如果緩存過期了籍救,或者為
// 釋放掉讀鎖习绢,然后獲取寫鎖 (后面會看到,沒釋放掉讀鎖就獲取寫鎖蝙昙,會發(fā)生死鎖情況)
rwl.readLock.unlock;
rwl.writeLock.lock;
try {
if (!cacheValid) { // 重新判斷闪萄,因為在等待寫鎖的過程中,可能前面有其他寫線程執(zhí)行過了
data = ...
cacheValid = true;
}
// 獲取讀鎖 (持有寫鎖的情況下奇颠,是允許獲取讀鎖的败去,稱為 “鎖降級”,反之不行烈拒。)
rwl.readLock.lock;
} finally {
// 釋放寫鎖圆裕,此時還剩一個讀鎖
rwl.writeLock.unlock; // Unlock write, still hold read
}
}
try {
use(data);
} finally {
// 釋放讀鎖
rwl.readLock.unlock;
}
}
}
ReentrantReadWriteLock 分為讀鎖和寫鎖兩個實例广鳍,讀鎖是共享鎖,可被多個線程同時使用吓妆,寫鎖是獨占鎖赊时。持有寫鎖的線程可以繼續(xù)獲取讀鎖,反之不行行拢。
2祖秒、ReentrantReadWriteLock 總覽
這一節(jié)比較重要,我們要先看清楚 ReentrantReadWriteLock 的大框架舟奠,然后再到源碼細(xì)節(jié)竭缝。
首先,我們來看下 ReentrantReadWriteLock 的結(jié)構(gòu)鸭栖,它有好些嵌套類:
大家先仔細(xì)看看這張圖中的信息歌馍。然后我們把 ReadLock 和 WriteLock 的代碼提出來一起看,清晰一些:
很清楚了晕鹊,ReadLock 和 WriteLock 中的方法都是通過 Sync 這個類來實現(xiàn)的松却。Sync 是 AQS 的子類,然后再派生了公平模式和不公平模式溅话。
從它們調(diào)用的 Sync 方法晓锻,我們可以看到:ReadLock 使用了共享模式,WriteLock 使用了獨占模式飞几。
等等砚哆,同一個 AQS 實例怎么可以同時使用共享模式和獨占模式?屑墨?躁锁?
這里給大家回顧下 AQS,我們橫向?qū)Ρ认?AQS 的共享模式和獨占模式:
AQS 的精髓在于內(nèi)部的屬性 state:
對于獨占模式來說卵史,通常就是 0 代表可獲取鎖战转,1 代表鎖被別人獲取了,重入例外
而共享模式下以躯,每個線程都可以對 state 進行加減操作
也就是說槐秧,獨占模式和共享模式對于 state 的操作完全不一樣,那讀寫鎖 ReentrantReadWriteLock 中是怎么使用 state 的呢忧设?
答案是將 state 這個 32 位的 int 值分為高 16 位和低 16位刁标,分別用于共享模式和獨占模式。
3址晕、源碼分析
有了前面的概念膀懈,大家心里應(yīng)該都有數(shù)了吧,下面就不再那么啰嗦了谨垃,直接代碼分析吏砂。
源代碼加注釋 1500 行撵儿,并不算難,我們要看的代碼量不大狐血。如果你前面一節(jié)都理解了淀歇,那么直接從頭開始一行一行往下看就是了,還是比較簡單的匈织。
ReentrantReadWriteLock 的前面幾行很簡單浪默,我們往下滑到 Sync 類,先來看下它的所有的屬性:
abstract static classSyncextendsAbstractQueuedSynchronizer{
// 下面這塊說的就是將 state 一分為二缀匕,高 16 位用于共享模式纳决,低16位用于獨占模式
static final int SHARED_SHIFT = 16;
static final int SHARED_UNIT = (1 << SHARED_SHIFT);
static final int MAX_COUNT = ( << SHARED_SHIFT) - 1 1;
static final int EXCLUSIVE_MASK = ( << SHARED_SHIFT) - 1 1;
// 取 c 的高 16 位值,代表讀鎖的獲取次數(shù)(包括重入)
static intsharedCount(int c) { return c >>> SHARED_SHIFT; }
// 取 c 的低 16 位值乡小,代表寫鎖的重入次數(shù)阔加,因為寫鎖是獨占模式
static intexclusiveCount(int c) { return c & EXCLUSIVE_MASK; }
// 這個嵌套類的實例用來記錄每個線程持有的讀鎖數(shù)量(讀鎖重入)
static final classHoldCounter{
// 持有的讀鎖數(shù)
int count = 0;
// 線程 id
final long tid = getThreadId(Thread.currentThread);
}
// ThreadLocal 的子類
static final classThreadLocalHoldCounter
extendsThreadLocal {
public HoldCounterinitialValue{
return new HoldCounter;
}
}
/**
* 組合使用上面兩個類,用一個 ThreadLocal 來記錄當(dāng)前線程持有的讀鎖數(shù)量
*/
private transient ThreadLocalHoldCounter readHolds;
// 用于緩存满钟,記錄"最后一個獲取讀鎖的線程"的讀鎖重入次數(shù)胜榔,
// 所以不管哪個線程獲取到讀鎖后,就把這個值占為已用湃番,這樣就不用到 ThreadLocal 中查詢 map 了
// 算不上理論的依據(jù):通常讀鎖的獲取很快就會伴隨著釋放夭织,
// 顯然,在 獲取->釋放 讀鎖這段時間吠撮,如果沒有其他線程獲取讀鎖的話尊惰,此緩存就能幫助提高性能
private transient HoldCounter cachedHoldCounter;
// 第一個獲取讀鎖的線程(并且其未釋放讀鎖),以及它持有的讀鎖數(shù)量
private transient Thread firstReader = ;
private transient int firstReaderHoldCount;
Sync {
// 初始化 readHolds 這個 ThreadLocal 屬性
readHolds = new ThreadLocalHoldCounter;
// 為了保證 readHolds 的內(nèi)存可見性
setState(getState); // ensures visibilityofreadHolds
}
...
}
state 的高 16 位代表讀鎖的獲取次數(shù)泥兰,包括重入次數(shù)弄屡,獲取到讀鎖一次加 1,釋放掉讀鎖一次減 1鞋诗。
state 的低 16 位代表寫鎖的獲取次數(shù)琢岩,因為寫鎖是獨占鎖,同時只能被一個線程獲得师脂,所以它代表重入次數(shù)。
每個線程都需要維護自己的 HoldCounter江锨,記錄該線程獲取的讀鎖次數(shù)吃警,這樣才能知道到底是不是讀鎖重入,用 ThreadLocal 屬性 readHolds 維護啄育。
cachedHoldCounter 有什么用酌心?其實沒什么用,但能提示性能挑豌。將最后一次獲取讀鎖的線程的 HoldCounter 緩存到這里安券,這樣比使用 ThreadLocal 性能要好一些,因為 ThreadLocal 內(nèi)部是基于 map 來查詢的。但是 cachedHoldCounter 這一個屬性畢竟只能緩存一個線程镊折,所以它要起提升性能作用的依據(jù)就是:通常讀鎖的獲取緊隨著就是該讀鎖的釋放夜畴。我這里可能表達不太好,但是大家應(yīng)該是懂的吧址貌。
firstReader 和 firstReaderHoldCount 有什么用铐拐?其實也沒什么用,但是它也能提示性能练对。將"第一個"獲取讀鎖的線程記錄在 firstReader 屬性中遍蟋,這里的第一個不是全局的概念,等這個 firstReader 當(dāng)前代表的線程釋放掉讀鎖以后螟凭,會有后來的線程占用這個屬性的虚青。firstReader 和 firstReaderHoldCount 使得在讀鎖不產(chǎn)生競爭的情況下,記錄讀鎖重入次數(shù)非常方便快速螺男。
如果一個線程使用了 firstReader棒厘,那么它就不需要占用 cachedHoldCounter。
個人認(rèn)為烟号,讀寫鎖源碼中最讓初學(xué)者頭疼的就是這幾個用于提升性能的屬性了绊谭,使得大家看得云里霧里的。主要是因為 ThreadLocal 內(nèi)部是通過一個 ThreadLocalMap 來操作的汪拥,會增加檢索時間达传。而很多場景下,執(zhí)行 unlock 的線程往往就是剛剛最后一次執(zhí)行 lock 的線程迫筑,中間可能沒有其他線程進行 lock宪赶。還有就是很多不怎么會發(fā)生讀鎖競爭的場景。
上面說了這么多脯燃,是希望能幫大家降低后面閱讀源碼的壓力搂妻,大家也可以先看看后面的,然后再慢慢體會辕棚。
前面我們好像都只說讀鎖欲主,完全沒提到寫鎖,主要是因為寫鎖真的是簡單很多逝嚎,我也特地將寫鎖的源碼放到了后面扁瓢,我們先啃下最難的讀鎖先。
4补君、讀鎖獲取
下面我就不一行一行按源碼順序說了引几,我們按照使用來說。
我們來看下讀鎖 ReadLock 的 lock 流程:
// ReadLock
public voidlock{
sync.acquireShared(1);
}
// AQS
public final voidacquireShared(int arg) {
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
然后我們就會進到 Sync 類的 tryAcquireShared 方法:
在 AQS 中挽铁,如果 tryAcquireShared(arg) 方法返回值小于 0 代表沒有獲取到共享鎖(讀鎖)伟桅,大于 0 代表獲取到敞掘。
回顧 AQS 共享模式:tryAcquireShared 方法不僅僅在 acquireShared 的最開始被使用,這里是 try楣铁,也就可能會失敗玖雁,如果失敗的話,執(zhí)行后面的 doAcquireShared民褂,進入到阻塞隊列茄菊,然后等待前驅(qū)節(jié)點喚醒。
喚醒以后赊堪,還是會調(diào)用 tryAcquireShared 進行獲取共享鎖的面殖。當(dāng)然,喚醒以后再 try 是很容易獲得鎖的哭廉,因為這個節(jié)點已經(jīng)排了很久的隊了脊僚,組織是會照顧它的。
所以遵绰,你在看下面這段代碼的時候辽幌,要想象到兩種獲取讀鎖的場景,一種是新來的椿访,一種是排隊排到它的乌企。
protected final inttryAcquireShared(int unused) {
Thread current = Thread.currentThread;
int c = getState;
// exclusiveCount(c) 不等于 0,說明有線程持有寫鎖成玫,
// 而且不是當(dāng)前線程持有寫鎖加酵,那么當(dāng)前線程獲取讀鎖失敗
// (另,如果持有寫鎖的是當(dāng)前線程哭当,是可以繼續(xù)獲取讀鎖的)
if (exclusiveCount(c) != 0 &&
getExclusiveOwnerThread != current)
return -1;
// 讀鎖的獲取次數(shù)
int r = sharedCount(c);
// 讀鎖獲取是否需要被阻塞猪腕,稍后細(xì)說。為了進去下面的分支钦勘,假設(shè)這里不阻塞就好了
if (!readerShouldBlock &&
// 判斷是否會溢出 (2^16-1陋葡,沒那么容易溢出的)
r < MAX_COUNT &&
// 下面這行 CAS 是將 state 屬性的高 16 位加 1,低 16 位不變彻采,如果成功就代表獲取到了讀鎖
compareAndSetState(c, c + SHARED_UNIT)) {
// =======================
// 進到這里就是獲取到了讀鎖
// =======================
if (r == 0) {
// r == 0 說明此線程是第一個獲取讀鎖的腐缤,或者說在它前面獲取讀鎖的都走光光了,它也算是第一個吧
// 記錄 firstReader 為當(dāng)前線程肛响,及其持有的讀鎖數(shù)量:1
firstReader = current;
firstReaderHoldCount = 1;
} else if (firstReader == current) {
// 進來這里岭粤,說明是 firstReader 重入獲取讀鎖(這非常簡單,count 加 1 結(jié)束)
firstReaderHoldCount++;
} else {
// 前面我們說了 cachedHoldCounter 用于緩存最后一個獲取讀鎖的線程
// 如果 cachedHoldCounter 緩存的不是當(dāng)前線程终惑,設(shè)置為緩存當(dāng)前線程的 HoldCounter
HoldCounter rh = cachedHoldCounter;
if (rh == || rh.tid != getThreadId(current))
cachedHoldCounter = rh = readHolds.get;
else if (rh.count == 0)
// 到這里,那么就是 cachedHoldCounter 緩存的是當(dāng)前線程门扇,但是 count 為 0雹有,
// 大家可以思考一下:這里為什么要 set ThreadLocal 呢偿渡?(當(dāng)然,答案肯定不在這塊代碼中)
// 既然 cachedHoldCounter 緩存的是當(dāng)前線程霸奕,
// 當(dāng)前線程肯定調(diào)用過 readHolds.get 進行初始化 ThreadLocal
readHolds.set(rh);
// count 加 1
rh.count++;
}
// return 大于 0 的數(shù)溜宽,代表獲取到了共享鎖
return 1;
}
// 往下看
return fullTryAcquireShared(current);
}
上面的代碼中,要進入 if 分支质帅,需要滿足:readerShouldBlock 返回 false适揉,并且 CAS 要成功(我們先不要糾結(jié) MAX_COUNT 溢出)。
那我們反向推煤惩,怎么樣進入到最后的 fullTryAcquireShared:
readerShouldBlock 返回 true嫉嘀,2 種情況:
在 FairSync 中說的是 hasQueuedPredecessors,即阻塞隊列中有其他元素在等待鎖魄揉。
也就是說剪侮,公平模式下,有人在排隊呢洛退,你新來的不能直接獲取鎖
在 NonFairSync 中說的是apparentlyFirstQueuedIsExclusive瓣俯,即判斷阻塞隊列中 head 的第一個后繼節(jié)點是否是來獲取寫鎖的,如果是的話兵怯,讓這個寫鎖先來彩匕,避免寫鎖饑餓。
作者給寫鎖定義了更高的優(yōu)先級媒区,所以如果碰上獲取寫鎖的線程馬上就要獲取到鎖了驼仪,獲取讀鎖的線程不應(yīng)該和它搶。
如果 head.next 不是來獲取寫鎖的驻仅,那么可以隨便搶谅畅,因為是非公平模式,大家比比 CAS 速度
compareAndSetState(c, c + SHARED_UNIT) 這里 CAS 失敗噪服,存在競爭毡泻。可能是和另一個讀鎖獲取競爭粘优,當(dāng)然也可能是和另一個寫鎖獲取操作競爭仇味。
然后就會來到 fullTryAcquireShared 中再次嘗試:
/**
* 1. 剛剛我們說了可能是因為 CAS 失敗,如果就此返回雹顺,那么就要進入到阻塞隊列了丹墨,
* 想想有點不甘心,因為都已經(jīng)滿足了 !readerShouldBlock嬉愧,也就是說本來可以不用到阻塞隊列的贩挣,
* 所以進到這個方法其實是增加 CAS 成功的機會
* 2. 在 NonFairSync 情況下,雖然 head.next 是獲取寫鎖的,我知道它等待很久了王财,我沒想和它搶卵迂,
* 可是如果我是來重入讀鎖的,那么只能表示對不起了
*/
final intfullTryAcquireShared(Thread current) {
HoldCounter rh = ;
// 別忘了這外層有個 for 循環(huán)
for (;;) {
int c = getState;
// 如果其他線程持有了寫鎖绒净,自然這次是獲取不到讀鎖了见咒,乖乖到阻塞隊列排隊吧
if (exclusiveCount(c) != 0) {
if (getExclusiveOwnerThread != current)
return -1;
// else we holdtheexclusive lock; blocking here
// would cause deadlock.
} else if (readerShouldBlock) {
/**
* 進來這里,說明:
* 1. exclusiveCount(c) == 0:寫鎖沒有被占用
* 2. readerShouldBlock 為 true挂疆,說明阻塞隊列中有其他線程在等待
*
* 既然 should block改览,那進來這里是干什么的呢?
* 答案:是進來處理讀鎖重入的缤言!
*
*/
// firstReader 線程重入讀鎖宝当,直接到下面的 CAS
if (firstReader == current) {
// assert firstReaderHoldCount > 0;
} else {
if (rh == ) {
rh = cachedHoldCounter;
if (rh == || rh.tid != getThreadId(current)) {
// cachedHoldCounter 緩存的不是當(dāng)前線程
// 那么到 ThreadLocal 中獲取當(dāng)前線程的 HoldCounter
// 如果當(dāng)前線程從來沒有初始化過 ThreadLocal 中的值,get 會執(zhí)行初始化
rh = readHolds.get;
// 如果發(fā)現(xiàn) count == 0墨闲,也就是說今妄,純屬上一行代碼初始化的,那么執(zhí)行 remove
// 然后往下兩三行鸳碧,乖乖排隊去
if (rh.count == 0)
readHolds.remove;
}
}
if (rh.count == 0)
// 排隊去盾鳞。
return -1;
}
/**
* 這塊代碼我看了蠻久才把握好它是干嘛的,原來只需要知道瞻离,它是處理重入的就可以了腾仅。
* 就是為了確保讀鎖重入操作能成功,而不是被塞到阻塞隊列中等待
*
* 另一個信息就是套利,這里對于 ThreadLocal 變量 readHolds 的處理:
* 如果 get 后發(fā)現(xiàn) count == 0推励,居然會做 remove 操作,
* 這行代碼對于理解其他代碼是有幫助的
*/
}
if (sharedCount(c) == MAX_COUNT)
throw new Error("Maximum lock count exceeded");
if (compareAndSetState(c, c + SHARED_UNIT)) {
// 這里 CAS 成功肉迫,那么就意味著成功獲取讀鎖了
// 下面需要做的是設(shè)置 firstReader 或 cachedHoldCounter
if (sharedCount(c) == 0) {
// 如果發(fā)現(xiàn) sharedCount(c) 等于 0验辞,就將當(dāng)前線程設(shè)置為 firstReader
firstReader = current;
firstReaderHoldCount = 1;
} else if (firstReader == current) {
firstReaderHoldCount++;
} else {
// 下面這幾行,就是將 cachedHoldCounter 設(shè)置為當(dāng)前線程
if (rh == )
rh = cachedHoldCounter;
if (rh == || rh.tid != getThreadId(current))
rh = readHolds.get;
else if (rh.count == 0)
readHolds.set(rh);
rh.count++;
cachedHoldCounter = rh;
}
// 返回大于 0 的數(shù)喊衫,代表獲取到了讀鎖
return 1;
}
}
}
firstReader 是每次將讀鎖獲取次數(shù)從 0 變?yōu)?1 的那個線程跌造。
能緩存到 firstReader 中就不要緩存到 cachedHoldCounter 中。
上面的源碼分析應(yīng)該說得非常詳細(xì)了族购,如果到這里你不太能看懂上面的有些地方的注釋壳贪,那么可以先往后看,然后再多看幾遍寝杖。
5违施、讀鎖釋放
下面我們看看讀鎖釋放的流程:
// ReadLock
public voidunlock{
sync.releaseShared(1);
}
// Sync
public final booleanreleaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared; // 這句代碼其實喚醒 獲取寫鎖的線程,往下看就知道了
return true;
}
return false;
}
// Sync
protected final booleantryReleaseShared(int unused) {
Thread current = Thread.currentThread;
if (firstReader == current) {
if (firstReaderHoldCount == 1)
// 如果等于 1瑟幕,那么這次解鎖后就不再持有鎖了磕蒲,把 firstReader 置為 留潦,給后來的線程用
// 為什么不順便設(shè)置 firstReaderHoldCount = 0?因為沒必要辣往,其他線程使用的時候自己會設(shè)值
firstReader = ;
else
firstReaderHoldCount--;
} else {
// 判斷 cachedHoldCounter 是否緩存的是當(dāng)前線程愤兵,不是的話要到 ThreadLocal 中取
HoldCounter rh = cachedHoldCounter;
if (rh == || rh.tid != getThreadId(current))
rh = readHolds.get;
int count = rh.count;
if (count <= 1) {
// 這一步將 ThreadLocal remove 掉,防止內(nèi)存泄漏排吴。因為已經(jīng)不再持有讀鎖了
readHolds.remove;
if (count <= 0)
// 就是那種,lock 一次懦鼠,unlock 好幾次的逗比
throw unmatchedUnlockException;
}
// count 減 1
--rh.count;
}
for (;;) {
int c = getState;
// nextc 是 state 高 16 位減 1 后的值
int nextc = c - SHARED_UNIT;
if (compareAndSetState(c, nextc))
// 如果 nextc == 0钻哩,那就是 state 全部 32 位都為 0,也就是讀鎖和寫鎖都空了
// 此時這里返回 true 的話肛冶,其實是幫助喚醒后繼節(jié)點中的獲取寫鎖的線程
return nextc == 0;
}
}
讀鎖釋放的過程還是比較簡單的街氢,主要就是將 hold count 減 1,如果減到 0 的話睦袖,還要將 ThreadLocal 中的 remove 掉珊肃。
然后是在 for 循環(huán)中將 state 的高 16 位減 1,如果發(fā)現(xiàn)讀鎖和寫鎖都釋放光了馅笙,那么喚醒后繼的獲取寫鎖的線程伦乔。
6、寫鎖獲取
寫鎖是獨占鎖董习。
如果有讀鎖被占用烈和,寫鎖獲取是要進入到阻塞隊列中等待的。
// WriteLock
public voidlock{
sync.acquire(1);
}
// AQS
public final voidacquire(int arg) {
if (!tryAcquire(arg) &&
// 如果 tryAcquire 失敗皿淋,那么進入到阻塞隊列等待
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt;
}
// Sync
protected final booleantryAcquire(int acquires) {
Thread current = Thread.currentThread;
int c = getState;
int w = exclusiveCount(c);
if (c != 0) {
// 看下這里返回 false 的情況:
// c != 0 && w == 0: 寫鎖可用招刹,但是有線程持有讀鎖(也可能是自己持有)
// c != 0 && w !=0 && current != getExclusiveOwnerThread: 其他線程持有寫鎖
// 也就是說,只要有讀鎖或?qū)戞i被占用窝趣,這次就不能獲取到寫鎖
if (w == 0 || current != getExclusiveOwnerThread)
return false;
if (w + exclusiveCount(acquires) > MAX_COUNT)
throw new Error("Maximum lock count exceeded");
// 這里不需要 CAS疯暑,仔細(xì)看就知道了,能到這里的哑舒,只可能是寫鎖重入妇拯,不然在上面的 if 就攔截了
setState(c + acquires);
return true;
}
// 如果寫鎖獲取不需要 block,那么進行 CAS散址,成功就代表獲取到了寫鎖
if (writerShouldBlock ||
!compareAndSetState(c, c + acquires))
return false;
setExclusiveOwnerThread(current);
return true;
}
下面看一眼 writerShouldBlock 的判定乖阵,然后你再回去看一篇寫鎖獲取過程。
static final classNonfairSyncextendsSync{
// 如果是非公平模式预麸,那么 lock 的時候就可以直接用 CAS 去搶鎖瞪浸,搶不到再排隊
final booleanwriterShouldBlock{
return false; // writers can always barge
}
...
}
static final classFairSyncextendsSync{
final booleanwriterShouldBlock{
// 如果是公平模式,那么如果阻塞隊列有線程等待的話吏祸,就乖乖去排隊
return hasQueuedPredecessors;
}
...
}
7对蒲、寫鎖釋放
// WriteLock
public voidunlock{
sync.release(1);
}
// AQS
public final booleanrelease(int arg) {
// 1. 釋放鎖
if (tryRelease(arg)) {
// 2. 如果獨占鎖釋放"完全"钩蚊,喚醒后繼節(jié)點
Node h = head;
if (h != && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
// Sync
// 釋放鎖,是線程安全的蹈矮,因為寫鎖是獨占鎖砰逻,具有排他性
// 實現(xiàn)很簡單,state 減 1 就是了
protected final booleantryRelease(int releases) {
if (!isHeldExclusively)
throw new IllegalMonitorStateException;
int nextc = getState - releases;
boolean free = exclusiveCount(nextc) == 0;
if (free)
setExclusiveOwnerThread;
setState(nextc);
// 如果 exclusiveCount(nextc) == 0泛鸟,也就是說包括重入的蝠咆,所有的寫鎖都釋放了,
// 那么返回 true北滥,這樣會進行喚醒后繼節(jié)點的操作刚操。
return free;
}
看到這里,是不是發(fā)現(xiàn)寫鎖相對于讀鎖來說要簡單很多再芋。
8菊霜、鎖降級
Doug Lea 沒有說寫鎖更高級,如果有線程持有讀鎖济赎,那么寫鎖獲取也需要等待鉴逞。
不過從源碼中也可以看出,確實會給寫鎖一些特殊照顧司训,如非公平模式下构捡,為了提高吞吐量,lock 的時候會先 CAS 競爭一下壳猜,能成功就代表讀鎖獲取成功了叭喜,但是如果發(fā)現(xiàn) head.next 是獲取寫鎖的線程,就不會去做 CAS 操作蓖谢。
Doug Lea 將持有寫鎖的線程捂蕴,去獲取讀鎖的過程稱為鎖降級(Lock downgrading)。這樣闪幽,此線程就既持有寫鎖又持有讀鎖啥辨。
但是,鎖升級是不可以的盯腌。線程持有讀鎖的話溉知,在沒釋放的情況下不能去獲取寫鎖,因為會發(fā)生死鎖腕够。
回去看下寫鎖獲取的源碼:
protected final booleantryAcquire(int acquires) {
Thread current = Thread.currentThread;
int c = getState;
int w = exclusiveCount(c);
if (c != 0) {
// 看下這里返回 false 的情況:
// c != 0 && w == 0: 寫鎖可用级乍,但是有線程持有讀鎖(也可能是自己持有)
// c != 0 && w !=0 && current != getExclusiveOwnerThread: 其他線程持有寫鎖
// 也就是說,只要有讀鎖或?qū)戞i被占用帚湘,這次就不能獲取到寫鎖
if (w == 0 || current != getExclusiveOwnerThread)
return false;
...
}
...
}
仔細(xì)想想玫荣,如果線程 a 先獲取了讀鎖,然后獲取寫鎖大诸,那么線程 a 就到阻塞隊列休眠了捅厂,自己把自己弄休眠了贯卦,而且可能之后就沒人去喚醒它了。
9焙贷、總結(jié)