Java多线程(十)之ReentrantReadWriteLock深入分析
一、ReentrantReadWriteLock與ReentrantLock
?
?
? 說(shuō)到ReentrantReadWriteLock,首先要做的是與ReentrantLock劃清界限。它和后者都是單獨(dú)的實(shí)現(xiàn),彼此之間沒(méi)有繼承或?qū)崿F(xiàn)的關(guān)系。
ReentrantLock?實(shí)現(xiàn)了標(biāo)準(zhǔn)的互斥操作,也就是一次只能有一個(gè)線程持有鎖,也即所謂獨(dú)占鎖的概念。前面的章節(jié)中一直在強(qiáng)調(diào)這個(gè)特點(diǎn)。顯然這個(gè)特點(diǎn)在一定程度上面減低了吞吐量,實(shí)際上獨(dú)占鎖是一種保守的鎖策略,在這種情況下任何“讀/讀”,“寫(xiě)/讀”,“寫(xiě)/寫(xiě)”操作都不能同時(shí)發(fā)生。但是同樣需要強(qiáng)調(diào)的一個(gè)概念是,鎖是有一定的開(kāi)銷的,當(dāng)并發(fā)比較大的時(shí)候,鎖的開(kāi)銷就比較客觀了。所以如果可能的話就盡量少用鎖,非要用鎖的話就嘗試看能否改造為讀寫(xiě)鎖。
ReadWriteLock?描述的是:一個(gè)資源能夠被多個(gè)讀線程訪問(wèn),或者被一個(gè)寫(xiě)線程訪問(wèn),但是不能同時(shí)存在讀寫(xiě)線程。也就是說(shuō)讀寫(xiě)鎖使用的場(chǎng)合是一個(gè)共享資源被大量讀取操作,而只有少量的寫(xiě)操作(修改數(shù)據(jù))。清單0描述了ReadWriteLock的API。
?
清單0 ReadWriteLock 接口
?
public interface ReadWriteLock {
Lock readLock();
Lock writeLock();
}
清單0描述的ReadWriteLock結(jié)構(gòu),這里需要說(shuō)明的是ReadWriteLock并不是Lock的子接口,只不過(guò)ReadWriteLock借助Lock來(lái)實(shí)現(xiàn)讀寫(xiě)兩個(gè)視角。在ReadWriteLock中每次讀取共享數(shù)據(jù)就需要讀取鎖,當(dāng)需要修改共享數(shù)據(jù)時(shí)就需要寫(xiě)入鎖??雌饋?lái)好像是兩個(gè)鎖,但其實(shí)不盡然,下文會(huì)指出。
?
?
二、ReentrantReadWriteLock的特性
?
?
?
ReentrantReadWriteLock有以下幾個(gè)特性:
- 公平性
- 非公平鎖(默認(rèn)) 這個(gè)和獨(dú)占鎖的非公平性一樣,由于讀線程之間沒(méi)有鎖競(jìng)爭(zhēng),所以讀操作沒(méi)有公平性和非公平性,寫(xiě)操作時(shí),由于寫(xiě)操作可能立即獲取到鎖,所以會(huì)推遲一個(gè)或多個(gè)讀操作或者寫(xiě)操作。因此非公平鎖的吞吐量要高于公平鎖。
- 公平鎖 利用AQS的CLH隊(duì)列,釋放當(dāng)前保持的鎖(讀鎖或者寫(xiě)鎖)時(shí),優(yōu)先為等待時(shí)間最長(zhǎng)的那個(gè)寫(xiě)線程分配寫(xiě)入鎖,當(dāng)前前提是寫(xiě)線程的等待時(shí)間要比所有讀線程的等待時(shí)間要長(zhǎng)。同樣一個(gè)線程持有寫(xiě)入鎖或者有一個(gè)寫(xiě)線程已經(jīng)在等待了,那么試圖獲取公平鎖的(非重入)所有線程(包括讀寫(xiě)線程)都將被阻塞,直到最先的寫(xiě)線程釋放鎖。如果讀線程的等待時(shí)間比寫(xiě)線程的等待時(shí)間還有長(zhǎng),那么一旦上一個(gè)寫(xiě)線程釋放鎖,這一組讀線程將獲取鎖。
- 重入性
- 讀寫(xiě)鎖允許讀線程和寫(xiě)線程按照請(qǐng)求鎖的順序重新獲取讀取鎖或者寫(xiě)入鎖。當(dāng)然了只有寫(xiě)線程釋放了鎖,讀線程才能獲取重入鎖。
- 寫(xiě)線程獲取寫(xiě)入鎖后可以再次獲取讀取鎖,但是讀線程獲取讀取鎖后卻不能獲取寫(xiě)入鎖。
- 另外讀寫(xiě)鎖最多支持65535個(gè)遞歸寫(xiě)入鎖和65535個(gè)遞歸讀取鎖。
- 鎖降級(jí)
- 寫(xiě)線程獲取寫(xiě)入鎖后可以獲取讀取鎖,然后釋放寫(xiě)入鎖,這樣就從寫(xiě)入鎖變成了讀取鎖,從而實(shí)現(xiàn)鎖降級(jí)的特性。
- 鎖升級(jí)
- 讀取鎖是不能直接升級(jí)為寫(xiě)入鎖的。因?yàn)楂@取一個(gè)寫(xiě)入鎖需要釋放所有讀取鎖,所以如果有兩個(gè)讀取鎖視圖獲取寫(xiě)入鎖而都不釋放讀取鎖時(shí)就會(huì)發(fā)生死鎖。
- 鎖獲取中斷
- 讀取鎖和寫(xiě)入鎖都支持獲取鎖期間被中斷。這個(gè)和獨(dú)占鎖一致。
- 條件變量
- 寫(xiě)入鎖提供了條件變量(Condition)的支持,這個(gè)和獨(dú)占鎖一致,但是讀取鎖卻不允許獲取條件變量,將得到一個(gè)UnsupportedOperationException異常。
- 重入數(shù)
- 讀取鎖和寫(xiě)入鎖的數(shù)量最大分別只能是65535(包括重入數(shù))。
?
?
?
三、ReentrantReadWriteLock的內(nèi)部實(shí)現(xiàn)
?
3.1?讀寫(xiě)鎖是獨(dú)占鎖的兩個(gè)不同視圖
?
ReentrantReadWriteLock里面的鎖主體就是一個(gè)Sync,也就是上面提到的FairSync或者NonfairSync,所以說(shuō)實(shí)際上只有一個(gè)鎖,只是在獲取讀取鎖和寫(xiě)入鎖的方式上不一樣,所以前面才有讀寫(xiě)鎖是獨(dú)占鎖的兩個(gè)不同視圖一說(shuō)。
ReentrantReadWriteLock里面有兩個(gè)類:ReadLock/WriteLock,這兩個(gè)類都是Lock的實(shí)現(xiàn)。
清單1 ReadLock 片段
?
public static class ReadLock implements Lock, java.io.Serializable {
private final Sync sync;
protected ReadLock(ReentrantReadWriteLock lock) {
sync = lock.sync;
}
public void lock() {
sync.acquireShared(1);
}
public void lockInterruptibly() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
public boolean tryLock() {
return sync.tryReadLock();
}
public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException {
return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
}
public void unlock() {
sync.releaseShared(1);
}
public Condition newCondition() {
throw new UnsupportedOperationException();
}
}
?
清單2 WriteLock 片段
?
public static class WriteLock implements Lock, java.io.Serializable {
private final Sync sync;
protected WriteLock(ReentrantReadWriteLock lock) {
sync = lock.sync;
}
public void lock() {
sync.acquire(1);
}
public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1);
}
public boolean tryLock( ) {
return sync.tryWriteLock();
}
public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException {
return sync.tryAcquireNanos(1, unit.toNanos(timeout));
}
public void unlock() {
sync.release(1);
}
public Condition newCondition() {
return sync.newCondition();
}
public boolean isHeldByCurrentThread() {
return sync.isHeldExclusively();
}
public int getHoldCount() {
return sync.getWriteHoldCount();
}
}
?
清單1描述的是讀鎖的實(shí)現(xiàn),清單2描述的是寫(xiě)鎖的實(shí)現(xiàn)。顯然WriteLock就是一個(gè)獨(dú)占鎖,這和ReentrantLock里面的實(shí)現(xiàn)幾乎相同,都是使用了AQS的acquire/release操作。當(dāng)然了在內(nèi)部處理方式上與ReentrantLock還是有一點(diǎn)不同的。對(duì)比清單1和清單2可以看到,ReadLock獲取的是共享鎖,WriteLock獲取的是獨(dú)占鎖。
?
3.2 ReentrantReadWriteLock中的state
?
在AQS章節(jié)中介紹到AQS中有一個(gè)state字段(int類型,32位)用來(lái)描述有多少線程獲持有鎖。在獨(dú)占鎖的時(shí)代這個(gè)值通常是0或者1(如果是重入的就是重入的次數(shù)),在共享鎖的時(shí)代就是持有鎖的數(shù)量。在上一節(jié)中談到,ReadWriteLock的讀、寫(xiě)鎖是相關(guān)但是又不一致的,所以需要兩個(gè)數(shù)來(lái)描述讀鎖(共享鎖)和寫(xiě)鎖(獨(dú)占鎖)的數(shù)量。顯然現(xiàn)在一個(gè)state就不夠用了。于是在ReentrantReadWrilteLock里面將這個(gè)字段一分為二,高位16位表示共享鎖的數(shù)量,低位16位表示獨(dú)占鎖的數(shù)量(或者重入數(shù)量)。2^16-1=65536,這就是上節(jié)中提到的為什么共享鎖和獨(dú)占鎖的數(shù)量最大只能是65535的原因了。
?
3.3?讀寫(xiě)鎖的獲取和釋放
?
有了上面的知識(shí)后再來(lái)分析讀寫(xiě)鎖的獲取和釋放就容易多了。
清單3 寫(xiě)入鎖獲取片段
?
protected final boolean tryAcquire(int acquires) {
Thread current = Thread.currentThread();
int c = getState();
int w = exclusiveCount(c);
if (c != 0) {
if (w == 0 || current != getExclusiveOwnerThread())
return false;
if (w + exclusiveCount(acquires) > MAX_COUNT)
throw new Error("Maximum lock count exceeded");
}
if ((w == 0 && writerShouldBlock(current)) ||
!compareAndSetState(c, c + acquires))
return false;
setExclusiveOwnerThread(current);
return true;
}
?
清單3 是寫(xiě)入鎖獲取的邏輯片段,整個(gè)工作流程是這樣的:
清單3 中 exclusiveCount(c)就是獲取寫(xiě)線程數(shù)(包括重入數(shù)),也就是state的低16位值。另外這里有一段邏輯是當(dāng)前寫(xiě)線程是否需要阻塞writerShouldBlock(current)。清單4 和清單5 就是公平鎖和非公平鎖中是否需要阻塞的片段。很顯然對(duì)于非公平鎖而言總是不阻塞當(dāng)前線程,而對(duì)于公平鎖而言如果AQS隊(duì)列不為空或者當(dāng)前線程不是在AQS的隊(duì)列頭那么就阻塞線程,直到隊(duì)列前面的線程處理完鎖邏輯。
清單4 公平讀寫(xiě)鎖寫(xiě)線程是否阻塞
?
final boolean writerShouldBlock(Thread current) {
return !isFirst(current);
}
?
清單5 非公平讀寫(xiě)鎖寫(xiě)線程是否阻塞
?
final boolean writerShouldBlock(Thread current) {
return false;
}
?
寫(xiě)入鎖的獲取邏輯清楚后,釋放鎖就比較簡(jiǎn)單了。清單6 描述的寫(xiě)入鎖釋放邏輯片段,其實(shí)就是檢測(cè)下剩下的寫(xiě)入鎖數(shù)量,如果是0就將獨(dú)占鎖線程清空(意味著沒(méi)有線程獲取鎖),否則就是說(shuō)當(dāng)前是重入鎖的一次釋放,所以不能將獨(dú)占鎖線程清空。然后將剩余線程狀態(tài)數(shù)寫(xiě)回AQS。
清單6 寫(xiě)入鎖釋放邏輯片段
?
protected final boolean tryRelease(int releases) {
int nextc = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
if (exclusiveCount(nextc) == 0) {
setExclusiveOwnerThread(null);
setState(nextc);
return true;
} else {
setState(nextc);
return false;
}
}
?
清單3~6 描述的寫(xiě)入鎖的獲取釋放過(guò)程。讀取鎖的獲取和釋放過(guò)程要稍微復(fù)雜些。 清單7描述的是讀取鎖的獲取過(guò)程。
清單7 讀取鎖獲取過(guò)程片段
?
protected final int tryAcquireShared(int unused) {
Thread current = Thread.currentThread();
int c = getState();
if (exclusiveCount(c) != 0 &&
getExclusiveOwnerThread() != current)
return -1;
if (sharedCount(c) == MAX_COUNT)
throw new Error("Maximum lock count exceeded");
if (!readerShouldBlock(current) &&
compareAndSetState(c, c + SHARED_UNIT)) {
HoldCounter rh = cachedHoldCounter;
if (rh == null || rh.tid != current.getId())
cachedHoldCounter = rh = readHolds.get();
rh.count++;
return 1;
}
return fullTryAcquireShared(current);
}
final int fullTryAcquireShared(Thread current) {
HoldCounter rh = cachedHoldCounter;
if (rh == null || rh.tid != current.getId())
rh = readHolds.get();
for (;;) {
int c = getState();
int w = exclusiveCount(c);
if ((w != 0 && getExclusiveOwnerThread() != current) ||
((rh.count | w) == 0 && readerShouldBlock(current)))
return -1;
if (sharedCount(c) == MAX_COUNT)
throw new Error("Maximum lock count exceeded");
if (compareAndSetState(c, c + SHARED_UNIT)) {
cachedHoldCounter = rh; // cache for release
rh.count++;
return 1;
}
}
}
?
讀取鎖獲取的過(guò)程是這樣的:
?
3.4 HoldCounter
?
在清單7 中有一個(gè)對(duì)象HoldCounter,這里暫且不提這是什么結(jié)構(gòu)和為什么存在這樣一個(gè)結(jié)構(gòu)。
接下來(lái)根據(jù)清單8 我們來(lái)看如何釋放一個(gè)讀取鎖。同樣先不理HoldCounter,關(guān)鍵的在于for循環(huán)里面,其實(shí)就是一個(gè)不斷嘗試的CAS操作,直到修改狀態(tài)成功。前面說(shuō)過(guò)state的高16位描述的共享鎖(讀取鎖)的數(shù)量,所以每次都需要減去2^16,這樣就相當(dāng)于讀取鎖數(shù)量減1。實(shí)際上SHARED_UNIT=1<<16。
清單8 讀取鎖釋放過(guò)程
?
protected final boolean tryReleaseShared(int unused) {
HoldCounter rh = cachedHoldCounter;
Thread current = Thread.currentThread();
if (rh == null || rh.tid != current.getId())
rh = readHolds.get();
if (rh.tryDecrement() <= 0)
throw new IllegalMonitorStateException();
for (;;) {
int c = getState();
int nextc = c - SHARED_UNIT;
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
?
好了,現(xiàn)在回頭看HoldCounter到底是一個(gè)什么東西。首先我們可以看到只有在獲取共享鎖(讀取鎖)的時(shí)候加1,也只有在釋放共享鎖的時(shí)候減1有作用,并且在釋放鎖的時(shí)候拋出了一個(gè)IllegalMonitorStateException異常。而我們知道IllegalMonitorStateException通常描述的是一個(gè)線程操作一個(gè)不屬于自己的監(jiān)視器對(duì)象的引發(fā)的異常。也就是說(shuō)這里的意思是一個(gè)線程釋放了一個(gè)不屬于自己或者不存在的共享鎖。
前面的章節(jié)中一再?gòu)?qiáng)調(diào),對(duì)于共享鎖,其實(shí)并不是鎖的概念,更像是計(jì)數(shù)器的概念。一個(gè)共享鎖就相對(duì)于一次計(jì)數(shù)器操作,一次獲取共享鎖相當(dāng)于計(jì)數(shù)器加1,釋放一個(gè)共享鎖就相當(dāng)于計(jì)數(shù)器減1。顯然只有線程持有了共享鎖(也就是當(dāng)前線程攜帶一個(gè)計(jì)數(shù)器,描述自己持有多少個(gè)共享鎖或者多重共享鎖),才能釋放一個(gè)共享鎖。否則一個(gè)沒(méi)有獲取共享鎖的線程調(diào)用一次釋放操作就會(huì)導(dǎo)致讀寫(xiě)鎖的state(持有鎖的線程數(shù),包括重入數(shù))錯(cuò)誤。
明白了HoldCounter的作用后我們就可以猜到它的作用其實(shí)就是當(dāng)前線程持有共享鎖(讀取鎖)的數(shù)量,包括重入的數(shù)量。那么這個(gè)數(shù)量就必須和線程綁定在一起。
在Java里面將一個(gè)對(duì)象和線程綁定在一起,就只有ThreadLocal才能實(shí)現(xiàn)了。所以毫無(wú)疑問(wèn)HoldCounter就應(yīng)該是綁定到線程上的一個(gè)計(jì)數(shù)器。
清單9 線程持有讀取鎖數(shù)量的計(jì)數(shù)器
?
static final class HoldCounter {
int count;
final long tid = Thread.currentThread().getId();
int tryDecrement() {
int c = count;
if (c > 0)
count = c - 1;
return c;
}
}
static final class ThreadLocalHoldCounter
extends ThreadLocal<HoldCounter> {
public HoldCounter initialValue() {
return new HoldCounter();
}
}
?
清單9 描述的是線程持有讀取鎖數(shù)量的計(jì)數(shù)器。可以看到這里使用ThreadLocal將HoldCounter綁定到當(dāng)前線程上,同時(shí)HoldCounter也持有線程Id,這樣在釋放鎖的時(shí)候才能知道ReadWriteLock里面緩存的上一個(gè)讀取線程(cachedHoldCounter)是否是當(dāng)前線程。這樣做的好處是可以減少ThreadLocal.get()的次數(shù),因?yàn)檫@也是一個(gè)耗時(shí)操作。需要說(shuō)明的是這樣HoldCounter綁定線程id而不綁定線程對(duì)象的原因是避免HoldCounter和ThreadLocal互相綁定而GC難以釋放它們(盡管GC能夠智能的發(fā)現(xiàn)這種引用而回收它們,但是這需要一定的代價(jià)),所以其實(shí)這樣做只是為了幫助GC快速回收對(duì)象而已。
除了readLock()和writeLock()外,Lock對(duì)象還允許tryLock(),那么ReadLock和WriteLock的tryLock()不一樣。清單10 和清單11 分別描述了讀取鎖的tryLock()和寫(xiě)入鎖的tryLock()。
讀取鎖tryLock()也就是tryReadLock()成功的條件是:沒(méi)有寫(xiě)入鎖或者寫(xiě)入鎖是當(dāng)前線程,并且讀線程共享鎖數(shù)量沒(méi)有超過(guò)65535個(gè)。
寫(xiě)入鎖tryLock()也就是tryWriteLock()成功的條件是: 沒(méi)有寫(xiě)入鎖或者寫(xiě)入鎖是當(dāng)前線程,并且嘗試一次修改state成功。
清單10 讀取鎖的tryLock()
?
final boolean tryReadLock() {
Thread current = Thread.currentThread();
for (;;) {
int c = getState();
if (exclusiveCount(c) != 0 &&
getExclusiveOwnerThread() != current)
return false;
if (sharedCount(c) == MAX_COUNT)
throw new Error("Maximum lock count exceeded");
if (compareAndSetState(c, c + SHARED_UNIT)) {
HoldCounter rh = cachedHoldCounter;
if (rh == null || rh.tid != current.getId())
cachedHoldCounter = rh = readHolds.get();
rh.count++;
return true;
}
}
}
?
清單11 寫(xiě)入鎖的tryLock()
?
final boolean tryWriteLock() {
Thread current = Thread.currentThread();
int c = getState();
if (c != 0) {
int w = exclusiveCount(c);
if (w == 0 ||current != getExclusiveOwnerThread())
return false;
if (w == MAX_COUNT)
throw new Error("Maximum lock count exceeded");
}
if (!compareAndSetState(c, c + 1))
return false;
setExclusiveOwnerThread(current);
return true;
}
?
四、小結(jié)
?
使用ReentrantReadWriteLock可以推廣到大部分讀,少量寫(xiě)的場(chǎng)景,因?yàn)樽x線程之間沒(méi)有競(jìng)爭(zhēng),所以比起sychronzied,性能好很多。如果需要較為精確的控制緩存,使用ReentrantReadWriteLock倒也不失為一個(gè)方案。
?
?
?
參考內(nèi)容來(lái)源:
ReentrantReadWriteLock ?http://uule.iteye.com/blog/1549707
深入淺出 Java Concurrency (13): 鎖機(jī)制 part 8 讀寫(xiě)鎖 (ReentrantReadWriteLock) (1)
http://www.blogjava.net/xylz/archive/2010/07/14/326080.html
深入淺出 Java Concurrency (14): 鎖機(jī)制 part 9 讀寫(xiě)鎖 (ReentrantReadWriteLock) (2)
http://www.blogjava.net/xylz/archive/2010/07/15/326152.html
高性能鎖ReentrantReadWriteLock
http://jhaij.iteye.com/blog/269656
JDK說(shuō)明
http://www.cjsdn.net/Doc/JDK60/java/util/concurrent/locks/ReentrantReadWriteLock.html
關(guān)于concurrent包 線程池、資源封鎖和隊(duì)列、ReentrantReadWriteLock介紹
http://www.oschina.net/question/16_636
總結(jié)
以上是生活随笔為你收集整理的Java多线程(十)之ReentrantReadWriteLock深入分析的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: Java多线程(十二)之线程池深入分析(
- 下一篇: java美元兑换,(Java实现) 美元