濟(jì)南網(wǎng)站制作創(chuàng)意百度信息流推廣
JUC第十三講:JUC鎖: ReentrantLock詳解
本文是JUC第十三講,JUC鎖:ReentrantLock詳解??芍厝腈i ReentrantLock 的底層是通過(guò) AbstractQueuedSynchronizer 實(shí)現(xiàn),所以先要學(xué)習(xí)上一章節(jié) AbstractQueuedSynchronizer 詳解。
文章目錄
- JUC第十三講:JUC鎖: ReentrantLock詳解
- 1、帶著B(niǎo)AT大廠(chǎng)的面試問(wèn)題去理解
- 2、ReentrantLock源碼分析
- 2.1、類(lèi)的繼承關(guān)系
- 2.2、類(lèi)的內(nèi)部類(lèi)
- 2.3、類(lèi)的屬性
- 2.4、類(lèi)的構(gòu)造函數(shù)
- 2.5、核心函數(shù)分析
- 3、示例分析
- 3.1、公平鎖
- 4、參考文章
1、帶著B(niǎo)AT大廠(chǎng)的面試問(wèn)題去理解
請(qǐng)帶著這些問(wèn)題繼續(xù)后文,會(huì)很大程度上幫助你更好的理解相關(guān)知識(shí)點(diǎn)。
- 什么是可重入,什么是可重入鎖? 它用來(lái)解決什么問(wèn)題? 一定程度避免死鎖
- ReentrantLock的核心是AQS,那么它怎么來(lái)實(shí)現(xiàn)的,繼承嗎? 說(shuō)說(shuō)其類(lèi)內(nèi)部結(jié)構(gòu)關(guān)系。獨(dú)占模式
- ReentrantLock是如何實(shí)現(xiàn)公平鎖的?
- ReentrantLock是如何實(shí)現(xiàn)非公平鎖的?
- ReentrantLock默認(rèn)實(shí)現(xiàn)的是公平還是非公平鎖? 非公平
- 使用ReentrantLock實(shí)現(xiàn)公平和非公平鎖的示例?
- ReentrantLock和Synchronized的對(duì)比?
2、ReentrantLock源碼分析
2.1、類(lèi)的繼承關(guān)系
ReentrantLock實(shí)現(xiàn)了Lock接口,Lock接口中定義了lock與unlock相關(guān)操作,并且還存在newCondition方法,表示生成一個(gè)條件。
public class ReentrantLock implements Lock, java.io.Serializable
2.2、類(lèi)的內(nèi)部類(lèi)
ReentrantLock總共有三個(gè)內(nèi)部類(lèi),并且三個(gè)內(nèi)部類(lèi)是緊密相關(guān)的,下面先看三個(gè)類(lèi)的關(guān)系。
說(shuō)明:ReentrantLock類(lèi)內(nèi)部總共存在Sync、NonfairSync、FairSync三個(gè)類(lèi),NonfairSync與FairSync類(lèi)繼承自Sync類(lèi),Sync類(lèi)繼承自 AbstractQueuedSynchronizer 抽象類(lèi)。下面逐個(gè)進(jìn)行分析。
- Sync類(lèi)
Sync類(lèi)的源碼如下:
abstract static class Sync extends AbstractQueuedSynchronizer {// 序列號(hào)private static final long serialVersionUID = -5179523762034025860L;// 獲取鎖abstract void lock();// 非公平方式獲取final boolean nonfairTryAcquire(int acquires) {// 當(dāng)前線(xiàn)程final Thread current = Thread.currentThread();// 獲取狀態(tài)int c = getState();// 表示沒(méi)有線(xiàn)程正在競(jìng)爭(zhēng)該鎖if (c == 0) {// 比較并設(shè)置狀態(tài)成功,狀態(tài)0表示鎖沒(méi)有被占用if (compareAndSetState(0, acquires)) {// 設(shè)置當(dāng)前線(xiàn)程獨(dú)占setExclusiveOwnerThread(current); return true; // 成功}}// 當(dāng)前線(xiàn)程擁有該鎖else if (current == getExclusiveOwnerThread()) {// 增加重入次數(shù)int nextc = c + acquires; if (nextc < 0) // overflowthrow new Error("Maximum lock count exceeded");// 設(shè)置狀態(tài)setState(nextc); // 成功return true; }// 失敗return false;}// 實(shí)現(xiàn)AQS提供的拓展點(diǎn)// 試圖在共享模式下獲取對(duì)象狀態(tài),此方法應(yīng)該查詢(xún)是否允許它在共享模式下獲取對(duì)象狀態(tài),如果允許,則獲取它protected final boolean tryRelease(int releases) {int c = getState() - releases;// 當(dāng)前線(xiàn)程不為獨(dú)占線(xiàn)程if (Thread.currentThread() != getExclusiveOwnerThread())throw new IllegalMonitorStateException(); // 拋出異常// 釋放標(biāo)識(shí)boolean free = false; if (c == 0) {free = true;// 已經(jīng)釋放,清空獨(dú)占setExclusiveOwnerThread(null); }// 設(shè)置標(biāo)識(shí)setState(c); return free; }// 判斷資源是否被當(dāng)前線(xiàn)程占有protected final boolean isHeldExclusively() {// While we must in general read state before owner,// we don't need to do so to check if current thread is ownerreturn getExclusiveOwnerThread() == Thread.currentThread();}// 新生一個(gè)條件final ConditionObject newCondition() {return new ConditionObject();}// Methods relayed from outer class// 返回資源的占用線(xiàn)程final Thread getOwner() {return getState() == 0 ? null : getExclusiveOwnerThread();}// 返回狀態(tài)final int getHoldCount() {return isHeldExclusively() ? getState() : 0;}// 資源是否被占用final boolean isLocked() {return getState() != 0;}/*** Reconstitutes the instance from a stream (that is, deserializes it).*/// 自定義反序列化邏輯private void readObject(java.io.ObjectInputStream s)throws java.io.IOException, ClassNotFoundException {s.defaultReadObject();setState(0); // reset to unlocked state}
}
Sync類(lèi)存在如下方法和作用如下。
- NonfairSync類(lèi)
NonfairSync類(lèi)繼承了Sync類(lèi),表示采用非公平策略獲取鎖,其實(shí)現(xiàn)了Sync類(lèi)中抽象的lock方法,源碼如下:
// 非公平鎖
static final class NonfairSync extends Sync {// 版本號(hào)private static final long serialVersionUID = 7316153563782823691L;// 獲得鎖final void lock() {// 比較并設(shè)置狀態(tài)成功,狀態(tài)0表示鎖沒(méi)有被占用if (compareAndSetState(0, 1))// 把當(dāng)前線(xiàn)程設(shè)置獨(dú)占了鎖setExclusiveOwnerThread(Thread.currentThread());else // 鎖已經(jīng)被占用,或者set失敗// 以獨(dú)占模式獲取對(duì)象,忽略中斷acquire(1);}protected final boolean tryAcquire(int acquires) {return nonfairTryAcquire(acquires);}
}
說(shuō)明:從lock方法的源碼可知,每一次都嘗試獲取鎖,而并不會(huì)按照公平等待的原則進(jìn)行等待,讓等待時(shí)間最久的線(xiàn)程獲得鎖。
- FairSync類(lèi)
FairSync類(lèi)也繼承了Sync類(lèi),表示采用公平策略獲取鎖,其實(shí)現(xiàn)了Sync類(lèi)中的抽象lock方法,源碼如下:
// 公平鎖
static final class FairSync extends Sync {// 版本序列化private static final long serialVersionUID = -3000897897090466540L;final void lock() {// 以獨(dú)占模式獲取對(duì)象,忽略中斷acquire(1);}/*** Fair version of tryAcquire. Don't grant access unless* recursive call or no waiters or is first.*/// 嘗試公平獲取鎖 AQS抽象類(lèi)提供的拓展點(diǎn)protected final boolean tryAcquire(int acquires) {// 獲取當(dāng)前線(xiàn)程final Thread current = Thread.currentThread();// 獲取狀態(tài)int c = getState();if (c == 0) { // 狀態(tài)為0// 不存在已經(jīng)等待更久的線(xiàn)程 并且比較并且設(shè)置狀態(tài)成功if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) {// 設(shè)置當(dāng)前線(xiàn)程獨(dú)占setExclusiveOwnerThread(current);return true;}}// 狀態(tài)不為0,即資源已經(jīng)被線(xiàn)程占據(jù)else if (current == getExclusiveOwnerThread()) {// 下一個(gè)狀態(tài)int nextc = c + acquires;if (nextc < 0) // 超過(guò)了int的表示范圍throw new Error("Maximum lock count exceeded");// 設(shè)置狀態(tài)setState(nextc);return true;}return false;}
}
說(shuō)明:跟蹤lock方法的源碼可知,當(dāng)資源空閑時(shí),它總是會(huì)先判斷sync隊(duì)列(AbstractQueuedSynchronizer中的數(shù)據(jù)結(jié)構(gòu))是否有等待時(shí)間更長(zhǎng)的線(xiàn)程,如果存在,則將該線(xiàn)程加入到等待隊(duì)列的尾部,實(shí)現(xiàn)了公平獲取原則。其中,FairSync類(lèi)的lock的方法調(diào)用如下,只給出了主要的方法。
說(shuō)明:可以看出只要資源被其他線(xiàn)程占用,該線(xiàn)程就會(huì)添加到sync queue中的尾部,而不會(huì)先嘗試獲取資源。這也是和Nonfair最大的區(qū)別,Nonfair每一次都會(huì)嘗試去獲取資源,如果此時(shí)該資源恰好被釋放,則會(huì)被當(dāng)前線(xiàn)程獲取,這就造成了不公平的現(xiàn)象,當(dāng)獲取不成功,再加入隊(duì)列尾部。
2.3、類(lèi)的屬性
ReentrantLock類(lèi)的sync非常重要,對(duì) ReentrantLock 類(lèi)的操作大部分都直接轉(zhuǎn)化為對(duì)Sync和 AbstractQueuedSynchronizer 類(lèi)的操作。
public class ReentrantLock implements Lock, java.io.Serializable {// 序列號(hào)private static final long serialVersionUID = 7373984872572414699L; // 同步隊(duì)列private final Sync sync;
}
2.4、類(lèi)的構(gòu)造函數(shù)
- ReentrantLock() 型構(gòu)造函數(shù)
默認(rèn)是采用的非公平策略獲取鎖
public ReentrantLock() {// 默認(rèn)非公平策略sync = new NonfairSync();
}
- ReentrantLock(boolean) 型構(gòu)造函數(shù)
可以傳遞參數(shù)確定采用公平策略或者是非公平策略,參數(shù)為true表示公平策略,否則,采用非公平策略:
public ReentrantLock(boolean fair) {sync = fair ? new FairSync() : new NonfairSync();
}
2.5、核心函數(shù)分析
通過(guò)分析ReentrantLock的源碼,可知對(duì)其操作都轉(zhuǎn)化為對(duì)Sync對(duì)象的操作,由于Sync繼承了AQS,所以基本上都可以轉(zhuǎn)化為對(duì)AQS的操作。如將ReentrantLock的lock函數(shù)轉(zhuǎn)化為對(duì)Sync的lock函數(shù)的調(diào)用,而具體會(huì)根據(jù)采用的策略(如公平策略或者非公平策略)的不同而調(diào)用到Sync的不同子類(lèi)。
所以可知,在ReentrantLock的背后,是AQS對(duì)其服務(wù)提供了支持,由于之前我們分析AQS的核心源碼,遂不再累贅。下面還是通過(guò)例子來(lái)更進(jìn)一步分析源碼。
3、示例分析
3.1、公平鎖
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;class MyThread extends Thread {private Lock lock;public MyThread(String name, Lock lock) {super(name);this.lock = lock;}public void run () {lock.lock();try {System.out.println(Thread.currentThread() + " running");try {Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}} finally {lock.unlock();}}
}public class AbstractQueuedSynchronizerDemo {public static void main(String[] args) throws InterruptedException {Lock lock = new ReentrantLock(true);MyThread t1 = new MyThread("t1", lock); MyThread t2 = new MyThread("t2", lock);MyThread t3 = new MyThread("t3", lock);t1.start();t2.start(); t3.start();}
}
運(yùn)行結(jié)果(某一次):
Thread[t1,5,main] running
Thread[t2,5,main] running
Thread[t3,5,main] running
說(shuō)明: 該示例使用的是公平策略,由結(jié)果可知,可能會(huì)存在如下一種時(shí)序。
說(shuō)明: 首先,t1線(xiàn)程的lock操作 -> t2線(xiàn)程的lock操作 -> t3線(xiàn)程的lock操作 -> t1線(xiàn)程的unlock操作 -> t2線(xiàn)程的unlock操作 -> t3線(xiàn)程的unlock操作。根據(jù)這個(gè)時(shí)序圖來(lái)進(jìn)一步分析源碼的工作流程。
- t1線(xiàn)程執(zhí)行l(wèi)ock.lock,下圖給出了方法調(diào)用中的主要方法。
說(shuō)明: 由調(diào)用流程可知,t1線(xiàn)程成功獲取了資源,可以繼續(xù)執(zhí)行。
- t2線(xiàn)程執(zhí)行 lock.lock,下圖給出了方法調(diào)用中的主要方法。
說(shuō)明: 由上圖可知,最后的結(jié)果是t2線(xiàn)程會(huì)被禁止,因?yàn)檎{(diào)用了LockSupport.park。
- t3線(xiàn)程執(zhí)行l(wèi)ock.lock,下圖給出了方法調(diào)用中的主要方法。
說(shuō)明:由上圖可知,最后的結(jié)果是t3線(xiàn)程會(huì)被禁止,因?yàn)檎{(diào)用了LockSupport.park。
- t1線(xiàn)程調(diào)用了lock.unlock,下圖給出了方法調(diào)用中的主要方法。
說(shuō)明:如上圖所示,最后,head的狀態(tài)會(huì)變?yōu)?,t2線(xiàn)程會(huì)被unpark,即t2線(xiàn)程可以繼續(xù)運(yùn)行。此時(shí)t3線(xiàn)程還是被禁止。
- t2獲得cpu資源,繼續(xù)運(yùn)行,由于t2之前被park了,現(xiàn)在需要恢復(fù)之前的狀態(tài),下圖給出了方法調(diào)用中的主要方法。
說(shuō)明:在setHead函數(shù)中會(huì)將head設(shè)置為之前head的下一個(gè)結(jié)點(diǎn),并且將pre域與thread域都設(shè)置為null,在acquireQueued返回之前,sync queue就只有兩個(gè)結(jié)點(diǎn)了。
- t2執(zhí)行l(wèi)ock.unlock,下圖給出了方法調(diào)用中的主要方法。
說(shuō)明: 由上圖可知,最終unpark t3線(xiàn)程,讓t3線(xiàn)程可以繼續(xù)運(yùn)行。
- t3線(xiàn)程獲取cpu資源,恢復(fù)之前的狀態(tài),繼續(xù)運(yùn)行。
說(shuō)明: 最終達(dá)到的狀態(tài)是sync queue中只剩下了一個(gè)結(jié)點(diǎn),并且該節(jié)點(diǎn)除了狀態(tài)為0外,其余均為null。
- t3執(zhí)行l(wèi)ock.unlock,下圖給出了方法調(diào)用中的主要方法。
說(shuō)明: 最后的狀態(tài)和之前的狀態(tài)是一樣的,隊(duì)列中有一個(gè)空節(jié)點(diǎn),頭節(jié)點(diǎn)為尾節(jié)點(diǎn)均指向它。
使用公平策略和Condition的情況可以參考上一篇關(guān)于A(yíng)QS的源碼示例分析部分,不再累贅。
4、參考文章
- 【JUC】JDK1.8源碼分析之ReentrantLock(三)