簡(jiǎn)介
ReentrantReadWriteLock是juc包下的一個(gè)讀寫鎖工具類餐济,該類內(nèi)部維護(hù)著一個(gè)讀鎖和一個(gè)寫鎖暴拄,通過讀鎖與寫鎖的分離使得在讀多寫少的壞境下性能有了很好的提升苦掘,可以減少線程的總等待時(shí)間畸写。
注意點(diǎn):
- 讀讀不阻塞贴铜,同一時(shí)間可以允許多個(gè)線程進(jìn)行讀鎖的獲取。
- 讀寫叹括、寫讀阻塞算墨,也就是讀鎖被線程獲取的時(shí)候,其他線程不能獲取寫鎖汁雷,寫鎖被線程獲取的時(shí)候其他線程不能進(jìn)行讀鎖的獲取净嘀。
主要特性:
1.公平性,ReentrantReadWriteLock構(gòu)造函數(shù)支持公平以及非公平模式侠讯。
2.可重入性挖藏,ReentrantReadWriteLock讀鎖與寫鎖都支持鎖的重入功能。
3.鎖降級(jí)厢漩,ReentrantReadWriteLock在獲取寫鎖的同時(shí)可以重入讀鎖熬苍,然后釋放鎖的時(shí)候先釋放寫鎖以達(dá)到鎖的降級(jí)。(注意ReentrantReadWriteLock只能進(jìn)行鎖降級(jí)不能進(jìn)行鎖升級(jí),鎖升級(jí)的時(shí)候會(huì)無限等待進(jìn)入死鎖)
ReentrantReadWriteLock內(nèi)部結(jié)構(gòu)
/*內(nèi)部讀鎖*/
private final ReentrantReadWriteLock.ReadLock readerLock;
/*內(nèi)部寫鎖*/
private final ReentrantReadWriteLock.WriteLock writerLock;
/*無參構(gòu)造器 默認(rèn)是給公平模式*/
public ReentrantReadWriteLock();
/*公平模式構(gòu)造器*/
public ReentrantReadWriteLock(boolean fair);
/*獲取寫鎖方法*/
public ReentrantReadWriteLock.WriteLock writeLock();
/*獲取讀鎖方法*/
public ReentrantReadWriteLock.ReadLock readLock();
/*內(nèi)部類同步器 基于aqs實(shí)現(xiàn)功能*/
abstract static class Sync extends AbstractQueuedSynchronizer{}
/*非公平sync*/
static final class NonfairSync extends Sync{}
/*公平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{}
ReentrantReadWriteLock內(nèi)部主要也是基于sync去實(shí)現(xiàn)的柴底,無論是讀鎖還是寫鎖都是基于sync去操作的,內(nèi)部都是操作的AbstractQueuedSynchronizer的state變量粱胜,那么如果用一個(gè)變量實(shí)現(xiàn)兩個(gè)鎖的狀態(tài)呢柄驻,這個(gè)地方就需要牽涉到位操作了,ReentrantReadWriteLock將變量32位的state拆分為高16位和低16位焙压,高位給讀鎖使用鸿脓,低位給寫鎖使用。具體操作代碼如下:
static final int SHARED_SHIFT = 16;
static final int SHARED_UNIT = (1 << SHARED_SHIFT);
static final int MAX_COUNT = (1 << SHARED_SHIFT) - 1;
static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;
static int sharedCount(int c) { return c >>> SHARED_SHIFT; }
static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }
ReadLock與WriteLock
ReadLock與WriteLock都是其ReentrantReadWriteLock類的內(nèi)部類涯曲,這兩個(gè)鎖都實(shí)現(xiàn)于lock接口野哭,也都重寫了相關(guān)的鎖獲取以及釋放方法,那么接下來我們就兩個(gè)鎖的鎖獲取以及釋放進(jìn)行分析幻件。
WriteLock鎖獲取以及鎖釋放原理
寫鎖的獲取
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
protected final boolean tryAcquire(int acquires) {
Thread current = Thread.currentThread();
int c = getState();
int w = exclusiveCount(c);
if (c != 0) {
// (Note: if c != 0 and w == 0 then shared count != 0)
if (w == 0 || current != getExclusiveOwnerThread())
return false;
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;
}
tryAcquire是寫鎖的獲取鎖的主要方法拨黔。首先獲取到state變量值c,然后對(duì)變量值state進(jìn)行c&((1 << 16) - 1)操作來獲取到當(dāng)前寫鎖的值绰沥,然后對(duì)判斷state是否等于0篱蝇。
- 如果等于0表示當(dāng)前沒有線程獲取到鎖,先查看當(dāng)前隊(duì)列是否有優(yōu)先的等待隊(duì)列(公平模式才會(huì)檢查徽曲,非公平模式直接返回false)零截,如果有直接返回true,沒有則對(duì)state值直接進(jìn)行cas替換操作秃臣,將c+acquires賦值給c涧衙,如果成功則設(shè)置exclusiveOwnerThread為當(dāng)前線程,失敗直接返回奥此。
- 如果不等于0(表示已經(jīng)有線程獲取到了鎖)弧哎,首先判斷w是否為0(也就是寫鎖是否被獲取)得院,如果寫鎖沒有被其他線程獲取或者獲取寫鎖的線程不是當(dāng)前線程則直接返回false 傻铣。其次判斷寫鎖的值是不是超過范圍,因?yàn)閷戞i與讀鎖的值被拆分為16位的數(shù)字祥绞,則該數(shù)字最大值為65535非洲,如果超過范圍直接拋出異常。上述條件都通過直接對(duì)c進(jìn)行運(yùn)算并賦值給c蜕径,這里大家可以關(guān)注到賦值并未做cas原子操作两踏,是因?yàn)樵摼€程已經(jīng)獲取到寫鎖了,變量操作是線程安全的所以不需要進(jìn)行cas操作兜喻。
寫鎖的釋放
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
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;
}
寫鎖的釋放主要邏輯在tryRelease方法中梦染,release只是調(diào)用了tryRelease方法,如果成功對(duì)其后繼節(jié)點(diǎn)進(jìn)行喚醒,如果后繼節(jié)點(diǎn)存在的話帕识。我們主要看下tryRelease方法邏輯泛粹,首先判斷占有寫鎖的線程是否是當(dāng)前線程,如果不是直接拋出異常IllegalMonitorStateException肮疗,如果是當(dāng)前線程則對(duì)state進(jìn)行計(jì)算晶姊,c=state-releases,如果c的低十六位數(shù)值為零伪货,則說明寫鎖已經(jīng)被釋放们衙,然后將ownerThread置為null,將c賦值給state返回c寫鎖的數(shù)值碱呼。這里在對(duì)state賦值的時(shí)候也沒有涉及cas操作蒙挑,原理是一樣的因?yàn)閷戞i是獨(dú)占鎖,然后獲取占有寫鎖的線程進(jìn)行變量替換都是線程安全的愚臀。
ReadLock鎖獲取以及鎖釋放原理
讀鎖的獲取
public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
/*嘗試獲取共享鎖*/
protected final int tryAcquireShared(int unused) {
Thread current = Thread.currentThread();
int c = getState();
if (exclusiveCount(c) != 0 &&
getExclusiveOwnerThread() != current)//1
return -1;
int r = sharedCount(c);//2
if (!readerShouldBlock() &&
r < MAX_COUNT &&
compareAndSetState(c, c + SHARED_UNIT)) {//3
if (r == 0) {//4
firstReader = current;
firstReaderHoldCount = 1;
} else if (firstReader == current) {//4
firstReaderHoldCount++;
} else {//4
HoldCounter rh = cachedHoldCounter;
if (rh == null || rh.tid != getThreadId(current))
cachedHoldCounter = rh = readHolds.get();
else if (rh.count == 0)
readHolds.set(rh);
rh.count++;
}
return 1;
}
return fullTryAcquireShared(current);
}
/*以不間斷模式進(jìn)行獲取共享鎖*/
private void doAcquireShared(int arg) {
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
if (p == head) {
int r = tryAcquireShared(arg);
if (r >= 0) {
setHeadAndPropagate(node, r);
p.next = null; // help GC
if (interrupted)
selfInterrupt();
failed = false;
return;
}
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
讀鎖的獲取稍微復(fù)雜一些忆蚀,先看下tryAcquireShared方法,tryAcquireShared是嘗試獲取鎖懊悯,如果失敗直接進(jìn)入到doAcquireShared方法中蜓谋。
tryAcquireShared方法:
1.首先判斷寫鎖是否被占有,如果被占有查看占有線程是否是當(dāng)前線程炭分,如果不是直接返回false桃焕,如果是則進(jìn)入讀鎖的獲取,這段邏輯也表示寫鎖可重入讀鎖捧毛,如果后續(xù)寫鎖先釋放那么也就達(dá)到了鎖的降級(jí)功能观堂。
2.獲取state高十六位數(shù)值(也就是當(dāng)前獲取讀鎖的數(shù)量),r = state>>>16呀忧。
3.查看當(dāng)前隊(duì)列aqs中是否有優(yōu)先的等待節(jié)點(diǎn)师痕,如果有直接跳過進(jìn)入fullTryAcquireShared,沒有進(jìn)行讀鎖數(shù)量判斷而账,不能大于等于最大數(shù)值65535胰坟,如果大于進(jìn)入fullTryAcquireShared,沒有進(jìn)行讀鎖數(shù)量+1并cas賦值泞辐,成功進(jìn)行下一步笔横,否則進(jìn)入fullTryAcquireShared方法。
4.進(jìn)入這一步表示讀鎖已經(jīng)成功獲取咐吼,接下來主要是對(duì)單個(gè)線程的讀鎖重入次數(shù)進(jìn)行更新吹缔,先查看r值,如果r值為零表示當(dāng)前沒有線程獲取讀鎖锯茄,則設(shè)置為firstReader為當(dāng)前線程厢塘,并且將firstReaderHoldCount值設(shè)置為1茶没,如果r不等于0(表示讀鎖已被其他線程獲取)晚碾,判斷firstReader==current抓半,如果相等進(jìn)行firstReaderHoldCount++操作,如果不相等從ThreadLocalHoldCounter對(duì)象中獲取HoldCounter迄薄,如果不存在創(chuàng)建HoldCounter放入ThreadLocalMap中琅关,并對(duì)HoldCounter的value做++操作。
注意:tryAcquireShared方法結(jié)尾處會(huì)調(diào)用fullTryAcquireShared讥蔽,fullTryAcquireShared這個(gè)方法其實(shí)也就是tryAcquireShared方法的自選模式盐类,里面的邏輯沒有太大的改變讯沈。
HoldCounter 用來儲(chǔ)存單個(gè)線程獲取讀鎖的重入數(shù)
cachedHoldCounter儲(chǔ)存最后一個(gè)獲取讀鎖的HoldCounter
firstReader表示第一個(gè)獲取讀鎖的線程脐供,只有一個(gè)線程獲取讀鎖的時(shí)候提高性能壹将,避免通過ThreadLocal去儲(chǔ)存择示。
firstReaderHoldCount表示第一個(gè)獲取讀鎖的線程的重入數(shù) 查辩,只有一個(gè)線程獲取讀鎖的時(shí)候提高性能并村,避免通過ThreadLocal去儲(chǔ)存嘉蕾。
doAcquireShared方法:
1.創(chuàng)建shared類型node排隊(duì)節(jié)點(diǎn)荚醒。然后進(jìn)入自旋代碼塊芋类。
2.獲取node的前置節(jié)點(diǎn),如果前置節(jié)點(diǎn)等于head界阁,進(jìn)入tryAcquireShared方法侯繁,如果tryAcquireShared執(zhí)行成功,則執(zhí)行進(jìn)行喚醒后繼節(jié)點(diǎn)泡躯,執(zhí)行完畢后返回贮竟。
3.判斷是否需要阻塞當(dāng)前線程,如果需要?jiǎng)t直接阻塞较剃。
讀鎖的釋放
protected final boolean tryReleaseShared(int unused) {
Thread current = Thread.currentThread();
if (firstReader == current) {
if (firstReaderHoldCount == 1)
firstReader = null;
else
firstReaderHoldCount--;
} else {
HoldCounter rh = cachedHoldCounter;
if (rh == null || rh.tid != 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;
}
}
讀鎖的釋放主要邏輯在tryReleaseShared方法中咕别,tryReleaseShared釋放讀鎖的邏輯也相對(duì)較簡(jiǎn)單。
1.獲取當(dāng)前線程對(duì)象写穴,判斷是否等于firstReader惰拱,如果等于則直接對(duì)firstReader以及firstReaderHoldCount進(jìn)行操作,如果不等于firstReader進(jìn)入下一步啊送。
2.從ThreadLocalMap中獲取HoldCounter對(duì)象偿短,判斷HoldCounter對(duì)象的count是否小于等于1,如果小于1則直接移除ThreadLocalMap中的HoldCounter删掀,然后對(duì)count進(jìn)行自減操作翔冀。
3.最后對(duì)state變量進(jìn)行操作,對(duì)aqs的state高十六位(讀鎖的數(shù)值)進(jìn)行減一操作披泪,然后進(jìn)行cas將新的讀鎖數(shù)值賦值給state變量纤子,然后返回結(jié)果。
總結(jié)
本文就ReentrantReadWriteLock的讀鎖與寫鎖的獲取以及釋放的源碼進(jìn)行了簡(jiǎn)單的分析,讀寫鎖最核心的原理是將state值進(jìn)行高16位以及低16位進(jìn)行切割管理控硼,以達(dá)到一個(gè)變量可同時(shí)實(shí)現(xiàn)讀寫鎖的操作泽论,其實(shí)ReentrantReadWriteLock內(nèi)部還有很多其他的方法,感興趣的小伙伴可以仔細(xì)閱讀下卡乾,ReentrantReadWriteLock適合在讀多寫少的場(chǎng)景使用翼悴,在寫多讀少的場(chǎng)景下其實(shí)效率并不比ReentrantLock的效率高,并且需要特別注意的是讀寫鎖可以進(jìn)行鎖降級(jí)幔妨,但是一定注意代碼不要出現(xiàn)鎖升級(jí)鹦赎,鎖升級(jí)會(huì)導(dǎo)致死鎖。