《Java并發(fā)編程之美》讀書(shū)筆記
獨(dú)占鎖ReentrantLock的原理
類(lèi)圖結(jié)構(gòu)
ReentrantLock是可重入的獨(dú)占鎖茅郎,同時(shí)只能有一個(gè)線程可以獲取到該鎖,其他獲取該鎖的線程會(huì)被阻塞返給到AQS阻塞隊(duì)里面涝缝。
從類(lèi)圖看到窑眯,ReentrantLock最終還是基于AQS來(lái)實(shí)現(xiàn)的梭伐,并且能夠根據(jù)參數(shù)來(lái)決定其內(nèi)部是一個(gè)公平鎖還是非公平鎖,默認(rèn)是非公平鎖。
public ReentrantLock() {
sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
其中Sync類(lèi)直接繼承自AQS蚀苛,它的子類(lèi)NonfairSync和FairSync分別實(shí)現(xiàn)了獲取鎖的非公平策略玩郊。
在這里,AQS的狀態(tài)值state表示該線程獲取鎖的可重入次數(shù),在默認(rèn)情況下枉阵,state的指表示當(dāng)前鎖沒(méi)有被任何線程持有译红,當(dāng)一個(gè)線程第一次獲取該鎖時(shí)會(huì)嘗試使用CAS設(shè)置state的狀態(tài)值為1,如果CAS成功則當(dāng)前線程獲取了該鎖兴溜,單后記錄該鎖的持有者為當(dāng)前線程侦厚。在該線程沒(méi)有釋放鎖的情況下第二次獲取該鎖后,狀態(tài)值被設(shè)置為2拙徽,這就是可重入次數(shù)刨沦,在該線程釋放該所時(shí),會(huì)嘗試使用CAS讓狀態(tài)值為1膘怕,如果減1后狀態(tài)值為0想诅,則當(dāng)前線程釋放該鎖。
獲取鎖
1.void lock()方法
當(dāng)一個(gè)線程調(diào)用該方法時(shí)候岛心,說(shuō)明該線程希望獲取該鎖来破,如果鎖當(dāng)前沒(méi)有被其他線程占用并且當(dāng)前線程之前沒(méi)有獲取過(guò)該鎖,則當(dāng)前線程會(huì)獲取該鎖忘古,然后設(shè)置當(dāng)前鎖的擁有者為當(dāng)前線程徘禁,并且AQS的狀態(tài)值為1,然后直接返回髓堪,如當(dāng)前線程之前已經(jīng)獲取過(guò)該鎖送朱,則這次簡(jiǎn)單的把AQS的狀態(tài)值加1.如果該鎖已經(jīng)被其他線程所擁有的娘荡,則調(diào)用該方法的線程會(huì)被放入到AQS阻塞隊(duì)列阻塞掛起。
public void lock() {
sync.acquire(1);
}
在如上的代碼中驶沼,ReentrantLock的lock()委托給了sync類(lèi)炮沐,根據(jù)創(chuàng)建的ReentrantLock構(gòu)造函數(shù)選擇實(shí)現(xiàn)的是NonfairSync還是FairSync,這個(gè)鎖是一個(gè)非公平鎖還是公平鎖回怜。這里先看sync子類(lèi)NonfairSync的情況也就是非公平鎖
非公平鎖
final void lock(){
//1.CAS設(shè)置當(dāng)前值
if(compareAndSet(0,1)){
setExclusiveOwnerThread(Thread.currentThread)大年;
}else{
//2.調(diào)用AQS的acquire方法
acquire(1);
}
}
在代碼1中,因?yàn)槟J(rèn)AQS的狀態(tài)值為0鹉戚,所以第一個(gè)調(diào)用Lock的線程會(huì)通過(guò)CAS設(shè)置狀態(tài)值為1,CAS成功則表示當(dāng)前線程獲取到了鎖专控,然后setExclusiveOwnerThread設(shè)置該鎖的擁有者為當(dāng)前線程抹凳。
如果這時(shí)候有其他線程調(diào)用lock方法企圖獲取該鎖,CAS會(huì)失敗伦腐,然后會(huì)調(diào)用AQS的acquire方法赢底。注意,傳遞參數(shù)為1柏蘑。
public final void acquire(int arg) {
//調(diào)用ReentrantLock重寫(xiě)的tryAcquire方法幸冻。
if (!tryAcquire(arg) &&
//tryAcquire返回false會(huì)把當(dāng)前線程放入AQS阻塞隊(duì)列里面
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
AQS并沒(méi)有提供可用的tryAcquire方法,tryAcquire方法需要子類(lèi)自己定制化咳焚,所以這里代碼3會(huì)調(diào)用ReentrantLock自己重寫(xiě)的tryAcquire方法洽损。
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
@ReservedStackAccess
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
//4當(dāng)前AQS的狀態(tài)值為0
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
//5當(dāng)前線程是否是該鎖的持有者
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}//6
return false;
}
首先代碼4會(huì)查看當(dāng)前鎖的狀態(tài)是否為0,為0則說(shuō)明當(dāng)前鎖空閑革半,那么就嘗試CAS獲取該鎖碑定,將AQS的狀態(tài)值從0設(shè)置為1,并設(shè)置當(dāng)前鎖的持有者為當(dāng)前線程然后但會(huì)true又官,如果當(dāng)前狀態(tài)不為0則說(shuō)明該鎖已經(jīng)被某個(gè)線程所持有延刘,所以代碼首先判斷當(dāng)前線程是否為該鎖的持有者,如果是則狀態(tài)值加1六敬,然后返回true碘赖,這里需要注意,nextc<0代表可重入次數(shù)溢出了外构。如果當(dāng)前線程不是鎖的持有者則返回false普泡,然后其會(huì)被放入AQS阻塞隊(duì)列。
介紹完了非公平鎖的實(shí)現(xiàn)代碼审编,再來(lái)關(guān)注非公平在這里是如何實(shí)現(xiàn)的劫哼。
首先非公平鎖是說(shuō)先嘗試獲取鎖的線程并不一定比后嘗試獲取鎖的線程優(yōu)先獲取鎖
這里假設(shè)線程A調(diào)用了lock方法執(zhí)行到nonfairTryAcquire的代碼4,發(fā)現(xiàn)當(dāng)前狀態(tài)值不為0則執(zhí)行代碼5割笙,發(fā)現(xiàn)當(dāng)前線程不是線程的持有者权烧,則執(zhí)行代碼6返回false,然后線程A被放入AQS阻塞隊(duì)列眯亦。
這時(shí)候線程B也調(diào)用了lock()方法執(zhí)行到nonfairTryAcquire的代碼4方法,發(fā)現(xiàn)當(dāng)前狀態(tài)值為0了(假設(shè)占有該鎖的其他線程釋放了該鎖)般码,所以通過(guò)CAS設(shè)置獲取到了該鎖妻率。明明是線程A先請(qǐng)求獲取該鎖的啊板祝?這就是非公平的體現(xiàn)宫静。
這里的線程B,在獲取鎖之前并沒(méi)有查看當(dāng)前AQS隊(duì)列里面是否有比自己更早請(qǐng)求該鎖的線程券时,而是采用了搶奪策略孤里。
公平鎖
@ReservedStackAccess
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
//7.當(dāng)前AQS狀態(tài)值為0
if (c == 0) {
//8.公平策略
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
//9.當(dāng)前線程是該鎖持有者
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
如上的代碼所示,公平鎖tryAcquire策略與非公平鎖類(lèi)似橘洞,不同之處策略捌袜,代碼8在設(shè)置CAS之前添加了hasQueuedPredecessors方法,該方法是實(shí)現(xiàn)公平性的核心代碼
public final boolean hasQueuedPredecessors() {
Node t=tail;
Node h=head;
Node s;
return h!=t&&((s=h.text)==null||s.thread!=Thread.currentThread());
}
在如上的代碼中炸枣,如果當(dāng)前線程節(jié)點(diǎn)有前驅(qū)節(jié)點(diǎn)則返回true虏等,否則如果當(dāng)前AQS隊(duì)列為空或者當(dāng)前線程節(jié)點(diǎn)是AQS第一個(gè)節(jié)點(diǎn)則返回false。如果h==t則說(shuō)明當(dāng)前AQS隊(duì)列為空适肠,直接返回false霍衫;如果h!=t并且s==null則說(shuō)明有一個(gè)元素將要作為AQS的第一個(gè)節(jié)點(diǎn)加入隊(duì)列(enq函數(shù)的第一個(gè)元素入隊(duì)列是兩步操作:首先常見(jiàn)一個(gè)哨兵頭結(jié)點(diǎn),然后將第一個(gè)元素插入哨兵節(jié)點(diǎn)后面)侯养,那么返回true敦跌,如果h!=t和s.thread!=Thread.currentThread()則說(shuō)明隊(duì)列里面的第一個(gè)元素不是當(dāng)前線程逛揩,則返回true峰髓。
void lockInterruptibly()方法
這個(gè)方法與lock()方法類(lèi)似,不同在于息尺,它對(duì)中斷進(jìn)行響應(yīng)携兵,就是當(dāng)前線程在調(diào)用該方法時(shí),如果其他線程調(diào)用了當(dāng)前線程的interrupt()方法搂誉,則當(dāng)前線程會(huì)拋出中斷異常InterruptedException異常徐紧,然后返回。
public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1);
}
public final void acquireInterruptibly(int arg)
throws InterruptedException {
//如果當(dāng)前線程被中斷炭懊,則直接拋出異常
if (Thread.interrupted())
throw new InterruptedException();
if (!tryAcquire(arg))
doAcquireInterruptibly(arg);
}
boolean tryLock()方法
嘗試獲取鎖并级,如果當(dāng)前鎖沒(méi)有被其他線程持有,則當(dāng)前線程獲取該鎖并返回true侮腹,否則返回false嘲碧。這方法不會(huì)引起線程阻塞
public boolean tryLock() {
return sync.nonfairTryAcquire(1);
}
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
boolean tryLock(long timeout,TimeUnit unit)
嘗試獲取鎖,與tryLock()的不同之處在于父阻,它設(shè)置了超時(shí)時(shí)間愈涩,如果超時(shí)時(shí)間沒(méi)有獲取到了該鎖則返回false
public boolean tryLock(long timeout, TimeUnit unit)
throws InterruptedException {
return sync.tryAcquireNanos(1, unit.toNanos(timeout));
}
釋放鎖 void unlock()方法
嘗試釋放鎖望抽,如果當(dāng)前線程持有該鎖,則調(diào)用該方法會(huì)讓該線程對(duì)持有AQS state狀態(tài)值減1履婉,如果減去1之后狀態(tài)值變?yōu)?狀態(tài)值為0煤篙,則當(dāng)前線程會(huì)釋放該鎖,否則僅僅減1而已毁腿。如果當(dāng)前線程沒(méi)有持有該鎖則會(huì)拋出IllegalMonitorStateException異常
public void unlock() {
sync.release(1);
}
@ReservedStackAccess
protected final boolean tryRelease(int releases) {
//11.如果不是鎖持有者調(diào)用unlock則拋出Unlock異常
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
//12.如果當(dāng)前的可重入次數(shù)為0辑奈,則從孔該鎖的持有線程
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
//13.設(shè)置可重入次數(shù)為原始值-1
setState(c);
return free;
}
如上代碼所示,如果當(dāng)前線程不是該鎖持有者則直接拋出異常已烤,否則查看狀態(tài)值是否為0鸠窗,為0則說(shuō)明當(dāng)前線程要放棄對(duì)該鎖的持有權(quán),則執(zhí)行代碼12把鎖的持有者為null胯究,如果狀態(tài)值不為0稍计,則僅僅讓當(dāng)前線程對(duì)該鎖的可重入次數(shù)減1.
使用ReentrantLock實(shí)現(xiàn)一個(gè)簡(jiǎn)單的線程安全的list;
public static class ReentrantLockList {
//線程不安全的List
private ArrayList<String> array=new ArrayList<>();
//獨(dú)占鎖
private volatile ReentrantLock lock=new ReentrantLock();
//添加元素
public void add(String e){
lock.lock();
try{
array.add(e);
}finally {
lock.unlock();
}
}
//刪除元素
public void remove(String e){
lock.lock();
try{
array.remove(e);
}finally {
lock.unlock();
}
}
//獲取數(shù)據(jù)
public String get(int index){
lock.lock();
try{
return array.get(index);
}finally {
lock.unlock();
}
}
}
如上代碼通過(guò)操作array元素前進(jìn)行加鎖保證了同一時(shí)間只有一個(gè)線程可以對(duì)arry進(jìn)行修改唐片,但是也只能有一個(gè)線程對(duì)array元素進(jìn)行訪問(wèn)丙猬。
如圖所示涨颜,假如線程Thread1,Thread2,Thread3同時(shí)嘗試獲取獨(dú)占鎖ReentrantLock费韭,假如Thread1獲取到了,那么Thread2和Thread3就會(huì)被轉(zhuǎn)換為Node 節(jié)點(diǎn)被放入ReentrantLock的AQS阻塞隊(duì)列庭瑰,而后被阻塞掛起星持。
如圖所示,假設(shè)Thread1獲取該鎖了之后調(diào)用了對(duì)應(yīng)鎖創(chuàng)建的條件變量1 await()方法,那么Thread1就會(huì)釋放獲取到的鎖,然后當(dāng)前線程就會(huì)被轉(zhuǎn)換為Node節(jié)點(diǎn)插入條件變量1的條件隊(duì)列帆调,由于Thread1釋放了鎖闪萄,鎖以阻塞到AQS隊(duì)列里面的Thread2和Thread3就有機(jī)會(huì)獲取到所=鎖,假如使用的公平策略玫膀,那么這時(shí)候Thread2會(huì)獲取到該鎖,從而從AQS隊(duì)列里面一出Thread2對(duì)應(yīng)的Node節(jié)點(diǎn)。
讀寫(xiě)鎖ReentrantReadWriteLock原理
類(lèi)圖結(jié)構(gòu)
解決線程安全問(wèn)題其實(shí)使用ReentrantLock就可以八回,但是ReentrantLock是獨(dú)占鎖,某時(shí)只有一個(gè)線程就可以獲取該鎖驾诈,而實(shí)際上會(huì)有寫(xiě)少讀多的場(chǎng)景缠诅,顯然ReentrantLock滿(mǎn)足不了需求,所以ReentrantReadWriteLock應(yīng)運(yùn)而生乍迄。ReentrantReadWriteLock采用讀寫(xiě)分離的策略管引,允許讀個(gè)線程可以同時(shí)獲取讀鎖。
讀寫(xiě)鎖的內(nèi)部維護(hù)了一個(gè)ReadLock和WriteLock,他們依賴(lài)Sync實(shí)現(xiàn)具體的功能闯两,而Sync繼承自AQS,并且也提供了公平鎖和非公平鎖的實(shí)現(xiàn)褥伴。我們知道AQS中值維護(hù)了一個(gè)state狀態(tài)谅将,一個(gè)state怎么表示讀和寫(xiě)兩種狀態(tài)呢?ReentrantReadWriteLock巧妙的運(yùn)用state的高16位表示讀狀態(tài)噩翠,也就是獲取到讀鎖的次數(shù);使用低16位表示獲取到寫(xiě)鎖的線程的可重入次數(shù)戏自。
static final int SHARED_SHIFT = 16;
//讀鎖(共享鎖)狀態(tài)單位指65536
static final int SHARED_UNIT = (1 << SHARED_SHIFT);
//讀鎖(共享鎖)線程最大的個(gè)數(shù)65535
static final int MAX_COUNT = (1 << SHARED_SHIFT) - 1;
//寫(xiě)鎖(排它鎖)掩碼,二進(jìn)制 15個(gè)1
static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;
//返回讀鎖線程數(shù)
static int sharedCount(int c) { return c >>> SHARED_SHIFT; }
//返回寫(xiě)鎖可重入個(gè)數(shù)
static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }
寫(xiě)鎖的獲取與釋放
在ReentrantReadWriteLock中寫(xiě)鎖使用writeLock來(lái)實(shí)現(xiàn)
void lock()
寫(xiě)鎖是一個(gè)獨(dú)占鎖伤锚,只有一個(gè)線程可以獲取資源擅笔。如果當(dāng)前沒(méi)有線程獲取到讀鎖和寫(xiě)鎖,則當(dāng)前線程可以獲取到寫(xiě)鎖然后返回屯援。如果當(dāng)前已有線程獲取讀鎖或者寫(xiě)鎖猛们,則當(dāng)前請(qǐng)求寫(xiě)鎖的線程就會(huì)被阻塞掛起。另外狞洋,寫(xiě)鎖是可重入鎖弯淘,如果當(dāng)前線程已經(jīng)獲取了該鎖,再次獲取知識(shí)簡(jiǎn)單的把可重入次數(shù)加1然后直接返回吉懊。
public void lock() {
sync.acquire(1);
}
public final void acquire(int arg) {
//syn重寫(xiě)tryAcquire方法
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
非公平寫(xiě)鎖的lock內(nèi)部調(diào)用了AQS的acquire的方法庐橙,其中tryAcquire是ReentrantReadWriteLock內(nèi)部類(lèi)sync類(lèi)重寫(xiě)的。
protected final boolean tryAcquire(int acquires) {
Thread current = Thread.currentThread();
int c = getState();
int w = exclusiveCount(c);
//1.說(shuō)明讀鎖或者寫(xiě)鎖已經(jīng)被謀線程獲取
if (c != 0) {
//2 w=0說(shuō)明已經(jīng)有線程獲取了讀鎖借嗽,w!=0表示當(dāng)前線程不是寫(xiě)鎖擁有者
if (w == 0 || current != getExclusiveOwnerThread())
return false;
//3.說(shuō)明當(dāng)前線程獲取了寫(xiě)鎖态鳖,判斷可重入次數(shù)
if (w + exclusiveCount(acquires) > MAX_COUNT)
throw new Error("Maximum lock count exceeded");
// 4.設(shè)置可重入次數(shù)+1
setState(c + acquires);
return true;
}
//5.第一個(gè)寫(xiě)線程獲取寫(xiě)鎖
if (writerShouldBlock() ||
!compareAndSetState(c, c + acquires))
return false;
setExclusiveOwnerThread(current);
return true;
}
在代碼1中,如果當(dāng)前AQS的狀態(tài)值不為0則說(shuō)明當(dāng)前已經(jīng)有線程獲取到了讀鎖或者寫(xiě)鎖恶导。在代碼2中浆竭,如果w==0說(shuō)明狀態(tài)值的低16位為0,而AQS狀態(tài)值不為0惨寿,則說(shuō)明高16位不為0邦泄,這暗示已經(jīng)有線程獲取到讀鎖,所以直接返回false裂垦;
而如果w!=0則說(shuō)明已有線程獲取了寫(xiě)鎖顺囊,在看當(dāng)前線程是不是該鎖的持有者,如果過(guò)不是就返回false蕉拢;
執(zhí)行到代碼3說(shuō)明當(dāng)前線程之前已經(jīng)獲取了讀寫(xiě)鎖特碳,所以判斷該線程的可重入數(shù)是不是超過(guò)了最大值,是則拋出異常企量,否則執(zhí)行代碼4增加當(dāng)前線程的可重入次數(shù)测萎,然后返回true。
如果AQS的狀態(tài)值為0則表示目前沒(méi)有線程獲取到讀鎖和寫(xiě)鎖届巩,所以執(zhí)行代碼5硅瞧,搶占式執(zhí)行CAS嘗試獲取寫(xiě)鎖,獲取成功則設(shè)置當(dāng)前鎖的持有者為當(dāng)前線程并返回true恕汇,否則返回false腕唧;
公平鎖的實(shí)現(xiàn)為:
final boolean writerShouldBlock() {
return hasQueuedPredecessors();
}
這里還是使用hasQueuedPredecessors判斷當(dāng)前線程節(jié)點(diǎn)是否有前驅(qū)節(jié)點(diǎn)或辖,如果有則當(dāng)前線程放棄獲取寫(xiě)鎖的權(quán)利,直接返回false枣接;
void lockInterruptibly()
類(lèi)似于lock()方法颂暇,它的不同之處在于,他會(huì)對(duì)中斷做出響應(yīng)但惶,也就是當(dāng)其他線程調(diào)用了該線程的interrupt()方法中斷了當(dāng)前線程時(shí)耳鸯,其會(huì)拋出InterruptedException
public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1);
}
Boolean trylock()
嘗試獲取寫(xiě)鎖,如果當(dāng)前沒(méi)有其他線程持有寫(xiě)鎖或者讀鎖膀曾,則當(dāng)前線程獲取寫(xiě)鎖會(huì)成功县爬,然后返回true,如果有返回false添谊,但是當(dāng)前線程并不會(huì)阻塞财喳。如果當(dāng)前線程已經(jīng)持有了讀寫(xiě)鎖后則簡(jiǎn)單增加AQS的狀態(tài)值后直接返回true。
public boolean tryLock() {
return sync.tryWriteLock();
}
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;
}
boolean tryLock(long timeout, TimeUnit unit)
與tryAcquire的不同之處在于斩狱,多了超時(shí)時(shí)間參數(shù)耳高,如果嘗試獲取寫(xiě)鎖失敗則會(huì)把當(dāng)前線程掛起指定的時(shí)間,帶到超時(shí)時(shí)間到后當(dāng)前線程被激活所踊,如果還是沒(méi)有獲取到寫(xiě)鎖則返回false泌枪。另外,該方法會(huì)對(duì)中斷進(jìn)行響應(yīng)污筷,也就是當(dāng)其他線程調(diào)用了該線程的interrupt()方法中斷了當(dāng)前線程時(shí)工闺,其會(huì)拋出InterruptedException
public boolean tryLock(long timeout, TimeUnit unit)
throws InterruptedException {
return sync.tryAcquireNanos(1, unit.toNanos(timeout));
}
void unlock()
嘗試釋放鎖乍赫,如果當(dāng)前線程持有該鎖瓣蛀,調(diào)用該方法會(huì)讓該線程對(duì)該線程持有的AQS狀態(tài)值減1,如果減去1后當(dāng)前狀態(tài)值為0則當(dāng)前線程會(huì)釋放該鎖雷厂,否則僅僅是減1而已惋增。如果當(dāng)前線程沒(méi)有持有該鎖而調(diào)用了這個(gè)方法則會(huì)拋出IllegalMonitor
public void unlock() {
sync.release(1);
}
public final boolean release(int arg) {
//調(diào)用ReentrantReadWriteLock中sync實(shí)現(xiàn)的tryRelease方法
if (tryRelease(arg)) {
//激活阻塞隊(duì)列里面的一個(gè)線程
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);//LockSupport里面的Unpark方法
return true;
}
return false;
}
protected final boolean tryRelease(int releases) {
//看是否是寫(xiě)鎖擁有者調(diào)用的unlock
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
//獲取可重入值,這里沒(méi)有高16位改鲫,因?yàn)楂@取寫(xiě)鎖時(shí)讀鎖的狀態(tài)肯定為0
int nextc = getState() - releases;
boolean free = exclusiveCount(nextc) == 0;
//如果寫(xiě)鎖可重入值為0則釋放鎖诈皿,否則只是簡(jiǎn)單的更新?tīng)顟B(tài)值。
if (free)
setExclusiveOwnerThread(null);
setState(nextc);
return free;
}
讀鎖的獲取與釋放
ReentrantReadWriteLock中的讀鎖時(shí)使用ReadLock來(lái)實(shí)現(xiàn)的
void lock()
獲取讀鎖像棘,如果當(dāng)前沒(méi)有其他線程持有寫(xiě)鎖稽亏,則當(dāng)前線程可以獲取讀鎖,AQS狀態(tài)指的高16位的值會(huì)增加1缕题,然后方法返回截歉,否則如果其他一個(gè)線程持有寫(xiě)鎖,則當(dāng)前線程就會(huì)被阻塞烟零。
public void lock() {
sync.acquireShared(1);
}
public final void acquireShared(int arg) {
//調(diào)用ReentrantReadWriteLock中sync實(shí)現(xiàn)的tryAcquireShared方法瘪松。
if (tryAcquireShared(arg) < 0)
//調(diào)用AQS的doAcquireShared方法
doAcquireShared(arg);
}
在如上的代碼中咸作,讀鎖的lock方法調(diào)用了AQS的acquireShared方法,在其內(nèi)部調(diào)用了ReentrantReadWriteLock中的sync重寫(xiě)的tryAcquireShared方法宵睦。
protected final int tryAcquireShared(int unused) {
//1.獲取當(dāng)前線程的狀態(tài)值
Thread current = Thread.currentThread();
int c = getState();
//2.判斷是否被寫(xiě)鎖占用
if (exclusiveCount(c) != 0 &&
getExclusiveOwnerThread() != current)
return -1;
//3獲取讀鎖計(jì)數(shù)
int r = sharedCount(c);
//4.嘗試獲取鎖记罚,讀個(gè)讀線程只有一個(gè)會(huì)成功,不成功的會(huì)進(jìn)入fullTryAcquireShared進(jìn)行重試
if (!readerShouldBlock() &&
r < MAX_COUNT &&
compareAndSetState(c, c + SHARED_UNIT)) {
//5.第一個(gè)線程獲取讀鎖
if (r == 0) {
firstReader = current;
firstReaderHoldCount = 1;
//6.如果當(dāng)前線程是第一個(gè)獲取讀鎖的線程
} else if (firstReader == current) {
firstReaderHoldCount++;
} else {
//7.記錄最后一個(gè)獲取讀鎖的線程獲記錄其他線程讀鎖的可重入數(shù)壳嚎。
HoldCounter rh = cachedHoldCounter;
if (rh == null ||
rh.tid != LockSupport.getThreadId(current))
cachedHoldCounter = rh = readHolds.get();
else if (rh.count == 0)
readHolds.set(rh);
rh.count++;
}
return 1;
}
//類(lèi)似于tryAcquireShared
return fullTryAcquireShared(current);
}
如上的代碼首先獲取了當(dāng)前AQS的狀態(tài)值桐智,然后代碼2查看是否有其他線程獲取到了寫(xiě)鎖,如果是則直接返回-1烟馅,而后調(diào)用AQS的doAcquireShared方法把當(dāng)前線程放入AQS阻塞隊(duì)列酵使。
如果當(dāng)前要獲取讀鎖的線程之前已經(jīng)持有了寫(xiě)鎖,則也可以獲取讀鎖焙糟,但是需要注意口渔,一個(gè)線程先獲取了寫(xiě)鎖,然后獲取了讀鎖處理事情完畢后穿撮,要記得把讀鎖和寫(xiě)鎖一起釋放掉缺脉,不能只釋放寫(xiě)鎖。
否則執(zhí)行代碼3悦穿,得到獲取到的讀鎖的個(gè)數(shù)攻礼,到這里就說(shuō)明沒(méi)有線程獲取到寫(xiě)鎖,但是可能有線程持有讀鎖栗柒,然后執(zhí)行代碼4礁扮,其中非公平鎖的readerShouldBlock實(shí)現(xiàn)代碼
final boolean readerShouldBlock() {
return apparentlyFirstQueuedIsExclusive();
}
final boolean apparentlyFirstQueuedIsExclusive() {
Node h, s;
return (h = head) != null &&
(s = h.next) != null &&
!s.isShared() &&
s.thread != null;
}
如上代碼的作用是,如果隊(duì)列里面存在一個(gè)元素瞬沦,則判斷第一個(gè)元素是不是正在嘗試獲取寫(xiě)鎖太伊,若不是,則當(dāng)前線程判斷當(dāng)前獲取讀鎖的線程是否已經(jīng)達(dá)到了最大值逛钻,最后執(zhí)行CAS操作將AQS狀態(tài)值的高16位加1.
代碼5僚焦,6記錄第一個(gè)獲取讀鎖的線程并統(tǒng)計(jì)該線程獲取讀鎖的可重入數(shù)。代碼7cachedHoldCounter記錄最后一個(gè)獲取到讀鎖的線程和該線程獲取讀鎖的可重入數(shù)曙痘,readHolds記錄了當(dāng)前線程獲取讀鎖的可重入數(shù)芳悲。
如果readerShouldBlock返回true則代表有線程正在獲取寫(xiě)鎖,所以執(zhí)行代碼8边坤,fullTryAcquireShared和tryAcquireShared類(lèi)似名扛,但是fullTryAcquireShared是通過(guò)自選獲取。
void lockInterruptibly()
類(lèi)似于lock()方法茧痒,它的不同之處在于肮韧,他會(huì)對(duì)中斷做出響應(yīng),也就是當(dāng)其他線程調(diào)用了該線程的interrupt()方法中斷了當(dāng)前線程時(shí),其會(huì)拋出InterruptedException
boolean tryLock()
嘗試獲取寫(xiě)鎖惹苗,如果當(dāng)前沒(méi)有其他線程持有寫(xiě)鎖殿较,則當(dāng)前線程獲取讀鎖會(huì)成功,然后返回true桩蓉,如果有返回false淋纲,但是當(dāng)前線程并不會(huì)阻塞。如果當(dāng)前線程已經(jīng)持有了讀鎖后則簡(jiǎn)單增加AQS的狀態(tài)值高16位后直接返回true院究。
boolean tryLock(long timeout,TimeUnit unit)
與tryLock()的不同之處在于洽瞬,多了超時(shí)時(shí)間參數(shù),如果嘗試獲取讀鎖失敗則會(huì)把當(dāng)前線程掛起指定的時(shí)間业汰,帶到超時(shí)時(shí)間到后當(dāng)前線程被激活伙窃,如果還是沒(méi)有獲取到讀鎖則返回false。另外样漆,該方法會(huì)對(duì)中斷進(jìn)行響應(yīng)为障,也就是當(dāng)其他線程調(diào)用了該線程的interrupt()方法中斷了當(dāng)前線程時(shí),其會(huì)拋出InterruptedException
void unlock()
public void unlock() {
sync.releaseShared(1);
}
如上代碼具體釋放鎖的操作是委托給Sync類(lèi)來(lái)做的
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
protected final boolean tryReleaseShared(int unused) {
Thread current = Thread.currentThread();
//循環(huán)知道自己的讀計(jì)數(shù)-1放祟,CAS更新成功鳍怨。
for (;;) {
int c = getState();
int nextc = c - SHARED_UNIT;
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
如以上代碼所示,在無(wú)線循環(huán)里面跪妥,首先會(huì)獲取當(dāng)前的AQS狀態(tài)值并將其保存到變量c鞋喇,然后變量c被減去一個(gè)讀計(jì)數(shù)單位后使用CAS去更新AQS的狀態(tài)值,如果更新成功則查看當(dāng)前的AQS的狀態(tài)值是否為0眉撵,為0則說(shuō)明當(dāng)前已經(jīng)沒(méi)有讀線程占用該鎖侦香,則tryReleaseShared返回try。然后會(huì)調(diào)用doReleaseShared方法釋放一個(gè)由于獲取寫(xiě)鎖而被阻塞的線程纽疟,如果當(dāng)前AQS的狀態(tài)值不為0罐韩,則說(shuō)明還有其他線程持有了讀鎖,所以tryReleaseShared返回false仰挣。如果tryReleaseShared中的CAS更新AQS狀態(tài)值失敗伴逸,則自旋重試直到成功缠沈。
基于ReentrantReadWriteLock實(shí)現(xiàn)線程安全的list
之前使用ReentrantLock實(shí)現(xiàn)線程安全的list膘壶,但是由于ReentrantLock是獨(dú)占鎖,所以在讀多寫(xiě)少的情況下性能很差洲愤。
public class ReentrantReadWriteLockList {
//線程不安全的List
private ArrayList<String> array=new ArrayList<>();
//獨(dú)占鎖
private volatile ReentrantReadWriteLock lock=new ReentrantReadWriteLock();
private final Lock readLock=lock.readLock();
private final Lock writeLock=lock.writeLock();
//添加元素
public void add(String e){
writeLock.lock();
try{
array.add(e);
}finally {
writeLock.unlock();
}
}
//刪除元素
public void remove(String e){
writeLock.lock();
try{
array.remove(e);
}finally {
writeLock.unlock();
}
}
//獲取數(shù)據(jù)
public String get(int index){
readLock.lock();
try{
return array.get(index);
}finally {
readLock.unlock();
}
}
}
以上代碼調(diào)用get的時(shí)候用的是讀鎖颓芭,這樣運(yùn)行多個(gè)讀線程來(lái)同時(shí)訪問(wèn)list的元素,這在讀多寫(xiě)少的情況下性能會(huì)更好柬赐。
參考資料:
《Java并發(fā)編程之美》