Java 讀寫鎖 ReentrantReadWriteLock 源碼分析

本文內(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é)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末撵割,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子辙芍,更是在濱河造成了極大的恐慌啡彬,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,820評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件故硅,死亡現(xiàn)場離奇詭異外遇,居然都是意外死亡,警方通過查閱死者的電腦和手機契吉,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,648評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來诡渴,“玉大人捐晶,你說我怎么就攤上這事⊥纾” “怎么了惑灵?”我有些...
    開封第一講書人閱讀 168,324評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長眼耀。 經(jīng)常有香客問我英支,道長,這世上最難降的妖魔是什么哮伟? 我笑而不...
    開封第一講書人閱讀 59,714評論 1 297
  • 正文 為了忘掉前任干花,我火速辦了婚禮,結(jié)果婚禮上楞黄,老公的妹妹穿的比我還像新娘池凄。我一直安慰自己,他們只是感情好鬼廓,可當(dāng)我...
    茶點故事閱讀 68,724評論 6 397
  • 文/花漫 我一把揭開白布肿仑。 她就那樣靜靜地躺著,像睡著了一般碎税。 火紅的嫁衣襯著肌膚如雪尤慰。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,328評論 1 310
  • 那天雷蹂,我揣著相機與錄音伟端,去河邊找鬼。 笑死匪煌,一個胖子當(dāng)著我的面吹牛荔泳,可吹牛的內(nèi)容都是我干的蕉饼。 我是一名探鬼主播,決...
    沈念sama閱讀 40,897評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼玛歌,長吁一口氣:“原來是場噩夢啊……” “哼昧港!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起支子,我...
    開封第一講書人閱讀 39,804評論 0 276
  • 序言:老撾萬榮一對情侶失蹤创肥,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后值朋,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體叹侄,經(jīng)...
    沈念sama閱讀 46,345評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,431評論 3 340
  • 正文 我和宋清朗相戀三年昨登,在試婚紗的時候發(fā)現(xiàn)自己被綠了趾代。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,561評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡丰辣,死狀恐怖撒强,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情笙什,我是刑警寧澤飘哨,帶...
    沈念sama閱讀 36,238評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站琐凭,受9級特大地震影響芽隆,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜统屈,卻給世界環(huán)境...
    茶點故事閱讀 41,928評論 3 334
  • 文/蒙蒙 一胚吁、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧愁憔,春花似錦囤采、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,417評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至思犁,卻和暖如春代虾,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背激蹲。 一陣腳步聲響...
    開封第一講書人閱讀 33,528評論 1 272
  • 我被黑心中介騙來泰國打工棉磨, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人学辱。 一個月前我還...
    沈念sama閱讀 48,983評論 3 376
  • 正文 我出身青樓乘瓤,卻偏偏與公主長得像环形,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子衙傀,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,573評論 2 359

推薦閱讀更多精彩內(nèi)容