此篇博客所有源碼均來自JDK 1.8
重入鎖ReentrantLock是排他鎖乱豆,排他鎖在同一時(shí)刻僅有一個(gè)線程可以進(jìn)行訪問茎芋,但是在大多數(shù)場景下遂庄,大部分時(shí)間都是提供讀服務(wù)乒融,而寫服務(wù)占有的時(shí)間較少古戴。然而讀服務(wù)不存在數(shù)據(jù)競爭問題欠橘,如果一個(gè)線程在讀時(shí)禁止其他線程讀勢必會導(dǎo)致性能降低。所以就提供了讀寫鎖现恼。
讀寫鎖維護(hù)著一對鎖肃续,一個(gè)讀鎖和一個(gè)寫鎖黍檩。通過分離讀鎖和寫鎖,使得并發(fā)性比一般的排他鎖有了較大的提升:在同一時(shí)間可以允許多個(gè)讀線程同時(shí)訪問始锚,但是在寫線程訪問時(shí)刽酱,所有讀線程和寫線程都會被阻塞。
讀寫鎖的主要特性:
- 公平性:支持公平性和非公平性瞧捌。
- 重入性:支持重入棵里。讀寫鎖最多支持65535個(gè)遞歸寫入鎖和65535個(gè)遞歸讀取鎖。
- 鎖降級:遵循獲取寫鎖姐呐、獲取讀鎖在釋放寫鎖的次序殿怜,寫鎖能夠降級成為讀鎖
讀寫鎖ReentrantReadWriteLock實(shí)現(xiàn)接口ReadWriteLock,該接口維護(hù)了一對相關(guān)的鎖曙砂,一個(gè)用于只讀操作稳捆,另一個(gè)用于寫入操作。只要沒有 writer麦轰,讀取鎖可以由多個(gè) reader 線程同時(shí)保持。寫入鎖是獨(dú)占的砖织。
public interface ReadWriteLock {
Lock readLock();
Lock writeLock();
}
ReadWriteLock定義了兩個(gè)方法款侵。readLock()返回用于讀操作的鎖,writeLock()返回用于寫操作的鎖侧纯。ReentrantReadWriteLock定義如下:
/** 內(nèi)部類 讀鎖 */
private final ReentrantReadWriteLock.ReadLock readerLock;
/** 內(nèi)部類 寫鎖 */
private final ReentrantReadWriteLock.WriteLock writerLock;
final Sync sync;
/** 使用默認(rèn)(非公平)的排序?qū)傩詣?chuàng)建一個(gè)新的 ReentrantReadWriteLock */
public ReentrantReadWriteLock() {
this(false);
}
/** 使用給定的公平策略創(chuàng)建一個(gè)新的 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 {
/**
* 省略其余源代碼
*/
}
public static class WriteLock implements Lock, java.io.Serializable{
/**
* 省略其余源代碼
*/
}
public static class ReadLock implements Lock, java.io.Serializable {
/**
* 省略其余源代碼
*/
}
ReentrantReadWriteLock與ReentrantLock一樣新锈,其鎖主體依然是Sync,它的讀鎖眶熬、寫鎖都是依靠Sync來實(shí)現(xiàn)的妹笆。所以ReentrantReadWriteLock實(shí)際上只有一個(gè)鎖,只是在獲取讀取鎖和寫入鎖的方式上不一樣而已娜氏,它的讀寫鎖其實(shí)就是兩個(gè)類:ReadLock拳缠、writeLock,這兩個(gè)類都是lock實(shí)現(xiàn)贸弥。
在ReentrantLock中使用一個(gè)int類型的state來表示同步狀態(tài)窟坐,該值表示鎖被一個(gè)線程重復(fù)獲取的次數(shù)。但是讀寫鎖ReentrantReadWriteLock內(nèi)部維護(hù)著兩個(gè)一對鎖绵疲,需要用一個(gè)變量維護(hù)多種狀態(tài)哲鸳。所以讀寫鎖采用“按位切割使用”的方式來維護(hù)這個(gè)變量,將其切分為兩部分盔憨,高16為表示讀徙菠,低16為表示寫。分割之后郁岩,讀寫鎖是如何迅速確定讀鎖和寫鎖的狀態(tài)呢婿奔?通過為運(yùn)算缺狠。假如當(dāng)前同步狀態(tài)為S,那么寫狀態(tài)等于 S & 0x0000FFFF(將高16位全部抹去)脸秽,讀狀態(tài)等于S >>> 16(無符號補(bǔ)0右移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; }
寫鎖
寫鎖就是一個(gè)支持可重入的排他鎖。
寫鎖的獲取
寫鎖的獲取最終會調(diào)用tryAcquire(int arg)记餐,該方法在內(nèi)部類Sync中實(shí)現(xiàn):
protected final boolean tryAcquire(int acquires) {
Thread current = Thread.currentThread();
//當(dāng)前鎖個(gè)數(shù)
int c = getState();
//寫鎖
int w = exclusiveCount(c);
if (c != 0) {
//c != 0 && w == 0 表示存在讀鎖
//當(dāng)前線程不是已經(jīng)獲取寫鎖的線程
if (w == 0 || current != getExclusiveOwnerThread())
return false;
//超出最大范圍
if (w + exclusiveCount(acquires) > MAX_COUNT)
throw new Error("Maximum lock count exceeded");
setState(c + acquires);
return true;
}
//是否需要阻塞
if (writerShouldBlock() ||
!compareAndSetState(c, c + acquires))
return false;
//設(shè)置獲取鎖的線程為當(dāng)前線程
setExclusiveOwnerThread(current);
return true;
}
該方法和ReentrantLock的tryAcquire(int arg)大致一樣驮樊,在判斷重入時(shí)增加了一項(xiàng)條件:讀鎖是否存在。因?yàn)橐_保寫鎖的操作對讀鎖是可見的片酝,如果在存在讀鎖的情況下允許獲取寫鎖囚衔,那么那些已經(jīng)獲取讀鎖的其他線程可能就無法感知當(dāng)前寫線程的操作。因此只有等讀鎖完全釋放后雕沿,寫鎖才能夠被當(dāng)前線程所獲取练湿,一旦寫鎖獲取了,所有其他讀审轮、寫線程均會被阻塞肥哎。
寫鎖的釋放
獲取了寫鎖用完了則需要釋放,WriteLock提供了unlock()方法釋放寫鎖:
public void unlock() {
sync.release(1);
}
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
寫鎖的釋放最終還是會調(diào)用AQS的模板方法release(int arg)方法疾渣,該方法首先調(diào)用tryRelease(int arg)方法嘗試釋放鎖篡诽,tryRelease(int arg)方法為讀寫鎖內(nèi)部類Sync中定義了,如下:
protected final boolean tryRelease(int releases) {
//釋放的線程不為鎖的持有者
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
int nextc = getState() - releases;
//若寫鎖的新線程數(shù)為0榴捡,則將鎖的持有者設(shè)置為null
boolean free = exclusiveCount(nextc) == 0;
if (free)
setExclusiveOwnerThread(null);
setState(nextc);
return free;
}
寫鎖釋放鎖的整個(gè)過程和獨(dú)占鎖ReentrantLock相似杈女,每次釋放均是減少寫狀態(tài),當(dāng)寫狀態(tài)為0時(shí)表示 寫鎖已經(jīng)完全釋放了吊圾,從而等待的其他線程可以繼續(xù)訪問讀寫鎖达椰,獲取同步狀態(tài),同時(shí)此次寫線程的修改對后續(xù)的線程可見项乒。
讀鎖
讀鎖為一個(gè)可重入的共享鎖啰劲,它能夠被多個(gè)線程同時(shí)持有,在沒有其他寫線程訪問時(shí)檀何,讀鎖總是或獲取成功呈枉。
讀鎖的獲取
讀鎖的獲取可以通過ReadLock的lock()方法:
public void lock() {
sync.acquireShared(1);
}
Sync的acquireShared(int arg)定義在AQS中:
public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
tryAcqurireShared(int arg)嘗試獲取讀同步狀態(tài),該方法主要用于獲取共享式同步狀態(tài)埃碱,獲取成功返回 >= 0的返回結(jié)果猖辫,否則返回 < 0 的返回結(jié)果。
protected final int tryAcquireShared(int unused) {
//當(dāng)前線程
Thread current = Thread.currentThread();
int c = getState();
//exclusiveCount(c)計(jì)算寫鎖
//如果存在寫鎖砚殿,且鎖的持有者不是當(dāng)前線程啃憎,直接返回-1
//存在鎖降級問題,后續(xù)闡述
if (exclusiveCount(c) != 0 &&
getExclusiveOwnerThread() != current)
return -1;
//讀鎖
int r = sharedCount(c);
/*
* readerShouldBlock():讀鎖是否需要等待(公平鎖原則)
* r < MAX_COUNT:持有線程小于最大數(shù)(65535)
* compareAndSetState(c, c + SHARED_UNIT):設(shè)置讀取鎖狀態(tài)
*/
if (!readerShouldBlock() &&
r < MAX_COUNT &&
compareAndSetState(c, c + SHARED_UNIT)) {
/*
* holdCount部分后面講解
*/
if (r == 0) {
firstReader = current;
firstReaderHoldCount = 1;
} else if (firstReader == current) {
firstReaderHoldCount++;
} else {
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);
}
**```**
****
讀鎖獲取的過程相對于獨(dú)占鎖而言會稍微復(fù)雜下似炎,整個(gè)過程如下:
1. 因?yàn)榇嬖阪i降級情況辛萍,如果存在寫鎖且鎖的持有者不是當(dāng)前線程則直接返回失敗悯姊,否則繼續(xù)
2. 依據(jù)公平性原則,判斷讀鎖是否需要阻塞贩毕,讀鎖持有線程數(shù)小于最大值(65535)悯许,且設(shè)置鎖狀態(tài)成功,執(zhí)行以下代碼(對于HoldCounter下面再闡述)辉阶,并返回1先壕。如果不滿足改條件,執(zhí)行fullTryAcquireShared()谆甜。
```java
final int fullTryAcquireShared(Thread current) {
HoldCounter rh = null;
for (;;) {
int c = getState();
//鎖降級
if (exclusiveCount(c) != 0) {
if (getExclusiveOwnerThread() != current)
return -1;
}
//讀鎖需要阻塞
else if (readerShouldBlock()) {
//列頭為當(dāng)前線程
if (firstReader == current) {
}
//HoldCounter后面講解
else {
if (rh == null) {
rh = cachedHoldCounter;
if (rh == null || rh.tid != getThreadId(current)) {
rh = readHolds.get();
if (rh.count == 0)
readHolds.remove();
}
}
if (rh.count == 0)
return -1;
}
}
//讀鎖超出最大范圍
if (sharedCount(c) == MAX_COUNT)
throw new Error("Maximum lock count exceeded");
//CAS設(shè)置讀鎖成功
if (compareAndSetState(c, c + SHARED_UNIT)) {
//如果是第1次獲取“讀取鎖”垃僚,則更新firstReader和firstReaderHoldCount
if (sharedCount(c) == 0) {
firstReader = current;
firstReaderHoldCount = 1;
}
//如果想要獲取鎖的線程(current)是第1個(gè)獲取鎖(firstReader)的線程,則將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);
//更新線程的獲取“讀取鎖”的共享計(jì)數(shù)
rh.count++;
cachedHoldCounter = rh; // cache for release
}
return 1;
}
}
}
fullTryAcquireShared(Thread current)會根據(jù)“是否需要阻塞等待”,“讀取鎖的共享計(jì)數(shù)是否超過限制”等等進(jìn)行處理规辱。如果不需要阻塞等待谆棺,并且鎖的共享計(jì)數(shù)沒有超過限制,則通過CAS嘗試獲取鎖罕袋,并返回1
讀鎖的釋放
與寫鎖相同改淑,讀鎖也提供了unlock()釋放讀鎖:
public void unlock() {
sync.releaseShared(1);
}
unlcok()方法內(nèi)部使用Sync的releaseShared(int arg)方法,該方法定義在AQS中:
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
調(diào)用tryReleaseShared(int arg)嘗試釋放讀鎖,該方法定義在讀寫鎖的Sync內(nèi)部類中:
protected final boolean tryReleaseShared(int unused) {
Thread current = Thread.currentThread();
//如果想要釋放鎖的線程為第一個(gè)獲取鎖的線程
if (firstReader == current) {
//僅獲取了一次,則需要將firstReader 設(shè)置null名秀,否則 firstReaderHoldCount - 1
if (firstReaderHoldCount == 1)
firstReader = null;
else
firstReaderHoldCount--;
}
//獲取rh對象,并更新“當(dāng)前線程獲取鎖的信息”
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;
}
//CAS更新同步狀態(tài)
for (;;) {
int c = getState();
int nextc = c - SHARED_UNIT;
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
HoldCounter
在讀鎖獲取鎖和釋放鎖的過程中,我們一直都可以看到一個(gè)變量rh (HoldCounter )询吴,該變量在讀鎖中扮演著非常重要的作用掠河。
我們了解讀鎖的內(nèi)在機(jī)制其實(shí)就是一個(gè)共享鎖,為了更好理解HoldCounter 猛计,我們暫且認(rèn)為它不是一個(gè)鎖的概率唠摹,而相當(dāng)于一個(gè)計(jì)數(shù)器。一次共享鎖的操作就相當(dāng)于在該計(jì)數(shù)器的操作奉瘤。獲取共享鎖勾拉,則該計(jì)數(shù)器 + 1,釋放共享鎖盗温,該計(jì)數(shù)器 - 1藕赞。只有當(dāng)線程獲取共享鎖后才能對共享鎖進(jìn)行釋放、重入操作卖局。所以HoldCounter的作用就是當(dāng)前線程持有共享鎖的數(shù)量斧蜕,這個(gè)數(shù)量必須要與線程綁定在一起,否則操作其他線程鎖就會拋出異常砚偶。我們先看HoldCounter的定義:
static final class HoldCounter {
int count = 0;
final long tid = getThreadId(Thread.currentThread());
}
HoldCounter 定義非常簡單批销,就是一個(gè)計(jì)數(shù)器count 和線程 id tid 兩個(gè)變量洒闸。按照這個(gè)意思我們看到HoldCounter 是需要和某給線程進(jìn)行綁定了,我們知道如果要將一個(gè)對象和線程綁定僅僅有tid是不夠的均芽,而且從上面的代碼我們可以看到HoldCounter 僅僅只是記錄了tid丘逸,根本起不到綁定線程的作用。那么怎么實(shí)現(xiàn)呢掀宋?答案是ThreadLocal深纲,定義如下:
static final class ThreadLocalHoldCounter
extends ThreadLocal<HoldCounter> {
public HoldCounter initialValue() {
return new HoldCounter();
}
}
通過上面代碼HoldCounter就可以與線程進(jìn)行綁定了。故而布朦,HoldCounter應(yīng)該就是綁定線程上的一個(gè)計(jì)數(shù)器囤萤,而ThradLocalHoldCounter則是線程綁定的ThreadLocal。從上面我們可以看到ThreadLocal將HoldCounter綁定到當(dāng)前線程上是趴,同時(shí)HoldCounter也持有線程Id涛舍,這樣在釋放鎖的時(shí)候才能知道ReadWriteLock里面緩存的上一個(gè)讀取線程(cachedHoldCounter)是否是當(dāng)前線程。這樣做的好處是可以減少ThreadLocal.get()的次數(shù)唆途,因?yàn)檫@也是一個(gè)耗時(shí)操作富雅。需要說明的是這樣HoldCounter綁定線程id而不綁定線程對象的原因是避免HoldCounter和ThreadLocal互相綁定而GC難以釋放它們(盡管GC能夠智能的發(fā)現(xiàn)這種引用而回收它們,但是這需要一定的代價(jià))肛搬,所以其實(shí)這樣做只是為了幫助GC快速回收對象而已没佑。
看到這里我們明白了HoldCounter作用了,我們在看一個(gè)獲取讀鎖的代碼段:
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
}
這段代碼涉及了幾個(gè)變量:firstReader 温赔、firstReaderHoldCount蛤奢、cachedHoldCounter 。我們先理清楚這幾個(gè)變量:
private transient Thread firstReader = null;
private transient int firstReaderHoldCount;
private transient HoldCounter cachedHoldCounter;
firstReader 看名字就明白了為第一個(gè)獲取讀鎖的線程陶贼,firstReaderHoldCount為第一個(gè)獲取讀鎖的重入數(shù)啤贩,cachedHoldCounter為HoldCounter的緩存。
理清楚上面所有的變量了拜秧,HoldCounter也明白了痹屹,我們就來給上面那段代碼標(biāo)明注釋,如下:
//如果獲取讀鎖的線程為第一次獲取讀鎖的線程枉氮,則firstReaderHoldCount重入數(shù) + 1
else if (firstReader == current) {
firstReaderHoldCount++;
} else {
//非firstReader計(jì)數(shù)
if (rh == null)
rh = cachedHoldCounter;
//rh == null 或者 rh.tid != current.getId()志衍,需要獲取rh
if (rh == null || rh.tid != getThreadId(current))
rh = readHolds.get();
//加入到readHolds中
else if (rh.count == 0)
readHolds.set(rh);
//計(jì)數(shù)+1
rh.count++;
cachedHoldCounter = rh; // cache for release
}
這里解釋下為何要引入firstRead、firstReaderHoldCount聊替。這是為了一個(gè)效率問題楼肪,firstReader是不會放入到readHolds中的,如果讀鎖僅有一個(gè)的情況下就會避免查找readHolds惹悄。
鎖降級
上開篇是LZ就闡述了讀寫鎖有一個(gè)特性就是鎖降級淹辞,鎖降級就意味著寫鎖是可以降級為讀鎖的,但是需要遵循先獲取寫鎖、獲取讀鎖在釋放寫鎖的次序象缀。注意如果當(dāng)前線程先獲取寫鎖蔬将,然后釋放寫鎖,再獲取讀鎖這個(gè)過程不能稱之為鎖降級央星,鎖降級一定要遵循那個(gè)次序霞怀。
在獲取讀鎖的方法tryAcquireShared(int unused)中,有一段代碼就是來判讀鎖降級的:
int c = getState();
//exclusiveCount(c)計(jì)算寫鎖
//如果存在寫鎖莉给,且鎖的持有者不是當(dāng)前線程毙石,直接返回-1
//存在鎖降級問題,后續(xù)闡述
if (exclusiveCount(c) != 0 &&
getExclusiveOwnerThread() != current)
return -1;
//讀鎖
int r = sharedCount(c);
鎖降級中讀鎖的獲取釋放為必要颓遏?肯定是必要的徐矩。試想,假如當(dāng)前線程A不獲取讀鎖而是直接釋放了寫鎖叁幢,這個(gè)時(shí)候另外一個(gè)線程B獲取了寫鎖滤灯,那么這個(gè)線程B對數(shù)據(jù)的修改是不會對當(dāng)前線程A可見的。如果獲取了讀鎖曼玩,則線程B在獲取寫鎖過程中判斷如果有讀鎖還沒有釋放則會被阻塞鳞骤,只有當(dāng)前線程A釋放讀鎖后,線程B才會獲取寫鎖成功黍判。
推薦閱讀
因?yàn)槔锩婧芏嗟胤缴婕暗搅薃QS部分豫尽,推薦閱讀如下部分:
- 【死磕Java并發(fā)】-----J.U.C之AQS:AQS簡介
- 【死磕Java并發(fā)】-----J.U.C之AQS:CLH同步隊(duì)列
- 【死磕Java并發(fā)】-----J.U.C之AQS:同步狀態(tài)的獲取與釋放
- 【死磕Java并發(fā)】-----J.U.C之AQS:阻塞和喚醒線程
參考資料
- Doug Lea:《Java并發(fā)編程實(shí)戰(zhàn)》
- 方騰飛:《Java并發(fā)編程的藝術(shù)》
- 【Java并發(fā)編程實(shí)戰(zhàn)】—–“J.U.C”:ReentrantReadWriteLock](http://cmsblogs.com/?p=1679))
- Java多線程(十)之ReentrantReadWriteLock深入分析