深入剖析Java ReentrantLock的源碼
ReentrantLock和Synchronized都是Java開(kāi)發(fā)中最常用的鎖,與Synchronized這種JVM內(nèi)置鎖不同的是,ReentrantLock提供了更豐富的語(yǔ)義。可以創(chuàng)建公平鎖或非公平鎖、響應(yīng)中斷、超時(shí)等待、按條件喚醒等。在某些場(chǎng)景下,使用ReentrantLock更適合,功能更強(qiáng)大。
前兩篇文章,我們分析了AQS的加鎖流程、以及源碼實(shí)現(xiàn)。當(dāng)時(shí)我們就說(shuō)了,AQS使用了模板設(shè)計(jì)模式,父類中定義加鎖流程,子類去實(shí)現(xiàn)具體的加鎖邏輯。所以大部分加鎖代碼已經(jīng)在父類AQS中實(shí)現(xiàn)了,導(dǎo)致ReentrantLock的源碼非常簡(jiǎn)單,一塊學(xué)習(xí)一下。
先看一下ReentrantLock怎么使用?
1. ReentrantLock的使用
/** * @author 一燈架構(gòu) * @apiNote ReentrantLock示例 **/ public class ReentrantLockDemo { public static void main(String[] args) { // 1. 創(chuàng)建ReentrantLock對(duì)象 ReentrantLock lock = new ReentrantLock(); // 2. 加鎖 lock.lock(); try { // 3. 這里執(zhí)行具體的業(yè)務(wù)邏輯 } finally { // 4. 釋放鎖 lock.unlock(); } } }
可以看到ReentrantLock的使用非常簡(jiǎn)單,調(diào)用lock加鎖,unlock釋放鎖,需要配置try/finally使用,保證在代碼執(zhí)行出錯(cuò)的時(shí)候也能釋放鎖。
ReentrantLock也可以配合Condition條件使用,具體可以翻一下前幾篇文章中BlockingQueue的源碼解析,那里面有ReentrantLock的實(shí)際使用。
再看一下ReentrantLock的類結(jié)構(gòu)
2. ReentrantLock類結(jié)構(gòu)
// 實(shí)現(xiàn)Lock接口 public class ReentrantLock implements Lock { // 只有一個(gè)Sync同步變量 private final Sync sync; // Sync繼承自AQS,主要邏輯都在這里面 abstract static class Sync extends AbstractQueuedSynchronizer { } // Sync的兩個(gè)子類,分別實(shí)現(xiàn)了公平鎖和非公平鎖 static final class FairSync extends Sync { } static final class NonfairSync extends Sync { } }
可以看出ReentrantLock的類結(jié)構(gòu)非常簡(jiǎn)單,實(shí)現(xiàn)了Lock接口。
類里面有兩個(gè)靜態(tài)內(nèi)部類,分別實(shí)現(xiàn)公平鎖和非公平鎖。
看一下Lock接口中,定義了哪些方法?
public interface Lock { // 加鎖 void lock(); // 加可中斷的鎖 void lockInterruptibly() throws InterruptedException; // 嘗試加鎖 boolean tryLock(); // 一段時(shí)間內(nèi),嘗試加鎖 boolean tryLock(long time, TimeUnit unit) throws InterruptedException; // 釋放鎖 void unlock(); // 新建條件狀態(tài) Condition newCondition(); }
就是一些使用鎖的常用方法。
在上篇文章中瀏覽AQS源碼的時(shí)候,了解到AQS定義了一些有關(guān)具體加鎖、釋放鎖的抽象方法,留給子類去實(shí)現(xiàn),再看一下有哪些抽象方法:
// 加獨(dú)占鎖 protected boolean tryAcquire(int arg) { throw new UnsupportedOperationException(); } // 釋放獨(dú)占鎖 protected boolean tryRelease(int arg) { throw new UnsupportedOperationException(); } // 加共享鎖 protected int tryAcquireShared(int arg) { throw new UnsupportedOperationException(); } // 釋放共享鎖 protected boolean tryReleaseShared(int arg) { throw new UnsupportedOperationException(); } // 判斷是否是當(dāng)前線程正在持有鎖 protected boolean isHeldExclusively() { throw new UnsupportedOperationException(); }
由于ReentrantLock使用的是獨(dú)占鎖,所以只需要實(shí)現(xiàn)獨(dú)占鎖相關(guān)的方法就可以了。
3. ReentrantLock源碼解析
3.1 ReentrantLock構(gòu)造方法
// 默認(rèn)的構(gòu)造方法,使用非公平鎖 public ReentrantLock() { sync = new NonfairSync(); } // 傳true,可以指定使用公平鎖 public ReentrantLock(boolean fair) { sync = fair ? new FairSync() : new NonfairSync(); }
在創(chuàng)建ReentrantLock對(duì)象的時(shí)候,可以指定使用公平鎖還是非公平鎖,默認(rèn)使用非公平鎖,顯然非公平鎖的性能更好。
先思考一個(gè)面試??紗?wèn)題,公平鎖和非公平鎖是怎么實(shí)現(xiàn)的?
3.2 非公平鎖源碼
先看一下加鎖源碼:
從父類ReentrantLock的加鎖方法入口:
public class ReentrantLock implements Lock { // 加鎖入口方法 public void lock() { // 調(diào)用Sync中加鎖方法 sync.lock(); } }
在子類NonfairSync的加鎖方法:
// 非公平鎖 static final class NonfairSync extends Sync { // 加鎖 final void lock() { // 1. 先嘗試加鎖(使用CAS設(shè)置state=1) if (compareAndSetState(0, 1)) // 2. 加鎖成功,就把當(dāng)前線程設(shè)置為持有鎖線程 setExclusiveOwnerThread(Thread.currentThread()); else // 3. 沒(méi)加鎖成功,再調(diào)用父類AQS中實(shí)際的加鎖邏輯 acquire(1); } }
加鎖邏輯也很簡(jiǎn)單,先嘗試使用CAS加鎖(也就是把state從0設(shè)置成1),加鎖成功,就把當(dāng)前線程設(shè)置為持有鎖線程。
設(shè)計(jì)者很聰明,在鎖競(jìng)爭(zhēng)不激烈的情況下,很大概率可以加鎖成功,也就不用走else中復(fù)雜的加鎖邏輯了。
如果沒(méi)有加鎖成功,還是需要走else中調(diào)用父類AQS的acquire方法,而acquire又需要調(diào)用子類的tryAcquire方法。
調(diào)用鏈路就是下面這樣:
根據(jù)調(diào)用鏈路,實(shí)際的加鎖邏輯在Sync.nonfairTryAcquire方法里面。
abstract static class Sync extends AbstractQueuedSynchronizer { // 非公平鎖的最終加鎖方法 final boolean nonfairTryAcquire(int acquires) { final Thread current = Thread.currentThread(); // 1. 獲取同步狀態(tài) int c = getState(); // 2. state=0表示無(wú)鎖,先嘗試加鎖(使用CAS設(shè)置state=1) if (c == 0) { if (compareAndSetState(0, acquires)) { // 3. 加鎖成功,就把當(dāng)前線程設(shè)置為持有鎖線程 setExclusiveOwnerThread(current); return true; } // 4. 如果當(dāng)前線程已經(jīng)持有鎖,執(zhí)行可重入的邏輯 } else if (current == getExclusiveOwnerThread()) { // 5. 加鎖次數(shù)+acquires int nextc = c + acquires; // 6. 超過(guò)tnt類型最大值,溢出了 if (nextc < 0) throw new Error("Maximum lock count exceeded"); setState(nextc); return true; } return false; } }
再看一下釋放鎖的調(diào)用流程,公平鎖和非公平鎖流程是一樣的,最終都是執(zhí)行Sync.tryRelease方法:
abstract static class Sync extends AbstractQueuedSynchronizer { // 釋放鎖 protected final boolean tryRelease(int releases) { // 1. 同步狀態(tài)減去釋放鎖次數(shù) int c = getState() - releases; // 2. 校驗(yàn)當(dāng)前線程不持有鎖,就報(bào)錯(cuò) if (Thread.currentThread() != getExclusiveOwnerThread()) throw new IllegalMonitorStateException(); boolean free = false; // 3. 判斷同步狀態(tài)是否等于0,無(wú)鎖后,就刪除持有鎖的線程 if (c == 0) { free = true; setExclusiveOwnerThread(null); } setState(c); return free; } }
再看一下公平鎖的源碼
3.3 公平鎖源碼
先看一下公平鎖的加鎖流程:
最終的加鎖方法是FairSync.tryAcquire,看一下具體邏輯:
static final class FairSync extends Sync { // 實(shí)現(xiàn)父類的加鎖邏輯 protected final boolean tryAcquire(int acquires) { final Thread current = Thread.currentThread(); // 1. 獲取同步狀態(tài) int c = getState(); // 2. state=0表示無(wú)鎖,先嘗試加鎖(使用CAS設(shè)置state=1) if (c == 0) { // 3. 判斷當(dāng)前線程是不是頭節(jié)點(diǎn)的下一個(gè)節(jié)點(diǎn)(講究先來(lái)后到) if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); return true; } // 4. 如果當(dāng)前線程已經(jīng)持有鎖,執(zhí)行可重入的邏輯 } else if (current == getExclusiveOwnerThread()) { // 5. 加鎖次數(shù)+acquires int nextc = c + acquires; // 6. 超過(guò)tnt類型最大值,溢出了 if (nextc < 0) throw new Error("Maximum lock count exceeded"); setState(nextc); return true; } return false; } // 判斷當(dāng)前線程是不是頭節(jié)點(diǎn)的下一個(gè)節(jié)點(diǎn)(講究先來(lái)后到) public final boolean hasQueuedPredecessors() { Node t = tail; Node h = head; Node s; return h != t && ((s = h.next) == null || s.thread != Thread.currentThread()); } }
公平鎖的釋放鎖邏輯跟非公平鎖一樣,上面已經(jīng)講過(guò)。
4. 總結(jié)
看完了ReentrantLock的所有源碼,是不是覺(jué)得ReentrantLock很簡(jiǎn)單。
由于加鎖流程的編排工作已經(jīng)在父類AQS中實(shí)現(xiàn),子類只需要實(shí)現(xiàn)具體的加鎖邏輯即可。
加鎖邏輯也很簡(jiǎn)單,也就是修改同步狀態(tài)state的值和持有鎖的線程exclusiveOwnerThread。
到此這篇關(guān)于深入剖析Java ReentrantLock的源碼的文章就介紹到這了,更多相關(guān)Java ReentrantLock內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- java ReentrantLock條件鎖實(shí)現(xiàn)原理示例詳解
- Java?synchornized與ReentrantLock處理并發(fā)出現(xiàn)的錯(cuò)誤
- 一文帶你掌握J(rèn)ava?ReentrantLock加解鎖原理
- 圖解Java?ReentrantLock的條件變量Condition機(jī)制
- 詳解Java?ReentrantLock可重入,可打斷,鎖超時(shí)的實(shí)現(xiàn)原理
- java?ReentrantLock并發(fā)鎖使用詳解
- 圖解Java?ReentrantLock公平鎖和非公平鎖的實(shí)現(xiàn)
- java鎖機(jī)制ReentrantLock源碼實(shí)例分析
- Java?AQS中ReentrantLock條件鎖的使用
相關(guān)文章
23種設(shè)計(jì)模式(19)java責(zé)任鏈模式
這篇文章主要為大家詳細(xì)介紹了23種設(shè)計(jì)模式之java責(zé)任鏈模式,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-01-01Java 常見(jiàn)的限流算法詳細(xì)分析并實(shí)現(xiàn)
大數(shù)據(jù)量高并發(fā)訪問(wèn)時(shí),經(jīng)常出現(xiàn)服務(wù)或接口面對(duì)暴漲的請(qǐng)求而不可用的情況,甚至引發(fā)連鎖反映導(dǎo)致整個(gè)系統(tǒng)崩潰。此時(shí)你需要使用的技術(shù)手段之一就是限流,當(dāng)請(qǐng)求達(dá)到一定的并發(fā)數(shù)或速率,就進(jìn)行等待、排隊(duì)、降級(jí)、拒絕服務(wù)等。限流時(shí),常見(jiàn)算法是計(jì)數(shù)器、漏斗、令牌桶算法2022-04-04Java調(diào)用第三方接口示范的實(shí)現(xiàn)
這篇文章主要介紹了Java調(diào)用第三方接口示范的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-09-09Spring使用@Filter注解創(chuàng)建自定義過(guò)濾器
Spring 中鮮為人知但非常有用的注解之一是 @Filter,它支持自定義過(guò)濾器,下面我們就來(lái)深入研究一下如何使用 Spring 的 @Filter 注解來(lái)創(chuàng)建自定義過(guò)濾器吧2023-11-11SpringBoot Controller Post接口單元測(cè)試示例
今天小編就為大家分享一篇關(guān)于SpringBoot Controller Post接口單元測(cè)試示例,小編覺(jué)得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來(lái)看看吧2018-12-12java 用泛型參數(shù)類型構(gòu)造數(shù)組詳解及實(shí)例
這篇文章主要介紹了java 用泛型參數(shù)類型構(gòu)造數(shù)組詳解及實(shí)例的相關(guān)資料,需要的朋友可以參考下2017-02-02java正則表達(dá)式獲取指定HTML標(biāo)簽的指定屬性值且替換的方法
下面小編就為大家?guī)?lái)一篇java正則表達(dá)式獲取指定HTML標(biāo)簽的指定屬性值且替換的方法。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2016-12-12springboot配置多個(gè)數(shù)據(jù)源兩種方式實(shí)現(xiàn)
在我們的實(shí)際業(yè)務(wù)中可能會(huì)遇到;在一個(gè)項(xiàng)目里面讀取多個(gè)數(shù)據(jù)庫(kù)的數(shù)據(jù)來(lái)進(jìn)行展示,spring對(duì)同時(shí)配置多個(gè)數(shù)據(jù)源是支持的,本文主要介紹了springboot配置多個(gè)數(shù)據(jù)源兩種方式實(shí)現(xiàn),感興趣的可以了解一下2022-03-03