Java多線程并發(fā)之ReentrantLock
ReentrantLock
公平鎖和非公平鎖
這個(gè)類是接口 Lock
的實(shí)現(xiàn)類,也是悲觀鎖的一種,但是它提供了 lock
和 unlock
方法用于主動(dòng)進(jìn)行鎖的加和拆。在之前使用的 sychronized
關(guān)鍵字是隱式加鎖機(jī)制,而它是顯示加鎖,同時(shí),這個(gè)類的構(gòu)造方法提供了公平和非公平的兩種機(jī)制。
什么是公平和非公平呢?就是多線程對(duì)共享資源進(jìn)行爭(zhēng)奪的時(shí)候,會(huì)出現(xiàn)一個(gè)線程或幾個(gè)線程完全占有共享資源,使得某些線程在長(zhǎng)時(shí)間處于等待狀態(tài)。公平就是要等待時(shí)間過長(zhǎng)的線程先獲得鎖。
而在 ReentrantLock
類中,提供了公平鎖和非公平鎖的使用。
在
ReentrantLock
源碼中,構(gòu)造器提供了一個(gè)參數(shù)入口,
public ReentrantLock(boolean fair) { sync = fair ? new FairSync() : new NonfairSync(); }
當(dāng)fair為true的時(shí)候,會(huì)創(chuàng)造一個(gè)
FairSync
對(duì)象給sync
屬性,FairSync
是繼承自Sync
的類,其中有一個(gè)Lock
方法,而在ReentrantLock的Lcok
中使用的是sync
屬性的Lock
方法,故能夠保證“公平”。使用非公平鎖就不需要在構(gòu)造器中傳參數(shù)。
在使用的時(shí)候,需要手動(dòng)上鎖和解鎖。
使用公平鎖,會(huì)將占優(yōu)勢(shì)的線程進(jìn)行限制,恢復(fù)掛起的線程,但是這個(gè)過程在CPU層面來(lái)講,是存在明顯時(shí)間差異的,非公平鎖的執(zhí)行效率相對(duì)更高,所以一般來(lái)說(shuō)不建議使用公平鎖,除非現(xiàn)實(shí)業(yè)務(wù)上需要符合實(shí)際需求。
重入鎖
ReentrantLock
本身還支持重入的功能。
重入鎖(Reentrant Lock)是一種支持重入的獨(dú)占鎖,它允許線程多次獲取同一個(gè)鎖,在釋放鎖之前必須相應(yīng)地多次釋放鎖。重入鎖通常由兩個(gè)操作組成:上鎖(lock)和解鎖(unlock)。當(dāng)一個(gè)線程獲取了重入鎖后,可以再次獲取該鎖而不被阻塞,同時(shí)必須通過相同數(shù)量的解鎖操作來(lái)釋放鎖。
重入鎖具有如下特點(diǎn):
- 重入性:重入鎖允許同一個(gè)線程多次獲取同一把鎖,避免了死鎖的發(fā)生。
- 獨(dú)占性:與公平鎖和非公平鎖一樣,重入鎖也是一種獨(dú)占鎖,同一時(shí)刻只能有一個(gè)線程持有該鎖。
- 可中斷性:重入鎖支持在等待鎖的過程中中斷該線程的執(zhí)行。
- 條件變量:在使用 java.util.concurrent.locks.Condition 類配合重入鎖實(shí)現(xiàn)等待/通知機(jī)制時(shí),等待狀態(tài)總是與重入鎖相關(guān)聯(lián)的。
重入鎖相對(duì)于 synchronized 關(guān)鍵字的優(yōu)勢(shì)在于,重入鎖具有更高的靈活性和擴(kuò)展性,支持公平鎖和非公平鎖、可中斷鎖和可輪詢鎖等特性,能夠更好地滿足多線程環(huán)境下的并發(fā)控制需要。synchroized
也有重入性。
ReentrantLock lock = new ReentrantLock(true); public void get(){ while(true){ try{ lock.lock(); lock.lock(); }catch(Exception exception){ }finally{ lock.unlock(); lock.unlock(); } } }
可重入的前提 lock
是同一個(gè)對(duì)象,而關(guān)鍵字 synchroized
的 Monitor
也是同一個(gè)對(duì)象充當(dāng),才能判定為重入。
public void get(){ while(true){ synchronized(this){ System.out.println("外層"); synchronized(this){ System.out.println("內(nèi)層"); } } } }
那么Java是怎么檢測(cè)鎖的重入和獲取鎖的次數(shù)的呢?在之前說(shuō)過的 ObjectMobitor
的C++源代碼中有 _recursions和_count
來(lái)記錄鎖的重入次數(shù)和線程獲取鎖的次數(shù)。這樣在Java層面就表示一個(gè)鎖對(duì)象都擁有一個(gè)鎖計(jì)數(shù)器 _count
和一個(gè)指向持有這個(gè)鎖的線程的指針 _owner
,只有當(dāng)前持有鎖的線程才能使得計(jì)數(shù)器+1,其他線程只有等待鎖被釋放(計(jì)數(shù)器置0)才能持有并+1。
在源碼中,非公平鎖的lock
方法如下:
//ReentrantLock類中: final void lock() { if (compareAndSetState(0, 1)) setExclusiveOwnerThread(Thread.currentThread()); else acquire(1); } //0的參數(shù)為是expect,是期望值,而1是update,是更新值
在執(zhí)行comparaAndSetState
方法的時(shí)候,它會(huì)詢問鎖的計(jì)數(shù)器(在底層執(zhí)行compareAndSwapInt
的本地方法),并期望數(shù)值為0,如果為0返回true
,然后設(shè)置執(zhí)行線程主是當(dāng)前線程。如果非0,那么他就會(huì)執(zhí)行acquire
:
//AbstractQueuedSynchronizer類中: public final void acquire(int arg) { if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); } //這里的tryAcquire,需要在其繼承的子類中進(jìn)一步實(shí)現(xiàn)對(duì)應(yīng)的功能 //子類可以根據(jù)自己的需要重新定義tryAcquire(int arg)的實(shí)現(xiàn)方式,從而實(shí)現(xiàn)更優(yōu)秀的鎖控制方案: //而在其子類FairSync中便覆蓋了這個(gè)方法 protected boolean tryAcquire(int arg) { throw new UnsupportedOperationException(); }
將線程放入等待隊(duì)列。
同時(shí)計(jì)數(shù)器是通過
unlock
來(lái)-1,所以lock和unlock
次數(shù)不匹配就會(huì)產(chǎn)生死鎖,也就是當(dāng)兩個(gè)線程調(diào)用同一個(gè)ReentrantLock
如果一個(gè)線程中的上鎖解鎖次數(shù)不相等,那么計(jì)數(shù)器沒有被清零,當(dāng)另一個(gè)線程請(qǐng)求鎖的時(shí)候,看到鎖計(jì)數(shù)器不是0,就認(rèn)為被的線程仍然持有它,所以一直等待它被釋放。需要了解底層的可以去看AQS中的release
方法。
而在 ReentrantLock
中有一個(gè)抽象內(nèi)部類 Sync
,它繼承自抽象類AbstractQueuedSynchronizer
(簡(jiǎn)稱AQS),這個(gè)類中有一個(gè)內(nèi)部 Node
類,當(dāng)有線程等待這把鎖的時(shí)候,會(huì)創(chuàng)建一個(gè)等待隊(duì)列,放置這些處于等待的線程。(AQS實(shí)現(xiàn)比較復(fù)雜,有興趣可以看看“竹子愛熊貓”大佬的文章。)
小結(jié)
在ReentrantLock
類中,有內(nèi)部類三個(gè),Sync,FairSync,NonfairSync
,他們的關(guān)系是Sync
是后兩個(gè)的父類,后兩個(gè)是兄弟類,同時(shí)Sync
繼承自AQS類,在AQS中有很多實(shí)現(xiàn)公平和非公平、可重入的機(jī)制,而具體實(shí)現(xiàn)效果的是Sync,FairSync,NonfairSync
。
疑惑
在下列代碼中,為什么在第一個(gè)線程的最后加上.join()
,沒有使得線程阻塞,而沒有它就會(huì)阻塞?
Lock lock = new ReentrantLock(); new CompletableFuture().runAsync(() -> { lock.lock(); try{ System.out.println(1); TimeUnit.SECONDS.sleep(2); }catch(Exception e){ }finally{ }}); //上面加上.join() new CompletableFuture().runAsync(() -> { lock.lock(); try{ System.out.println(2); }catch(Exception e){ }finally{ lock.unlock(); }}).join();
到此這篇關(guān)于Java多線程并發(fā)之ReentrantLock的文章就介紹到這了,更多相關(guān)ava ReentrantLock內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringBoot啟動(dòng)執(zhí)行sql腳本的3種方法實(shí)例
在應(yīng)用程序啟動(dòng)后,可以自動(dòng)執(zhí)行建庫(kù)、建表等SQL腳本,下面這篇文章主要給大家介紹了關(guān)于SpringBoot啟動(dòng)執(zhí)行sql腳本的3種方法,文中通過實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-01-01java常用工具類 Random隨機(jī)數(shù)、MD5加密工具類
這篇文章主要為大家詳細(xì)介紹了Java常用工具類,Random隨機(jī)數(shù)工具類、MD5加密工具類,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-05-05springboot @Valid注解對(duì)嵌套類型的校驗(yàn)功能
這篇文章主要介紹了springboot~@Valid注解對(duì)嵌套類型的校驗(yàn),主要介紹 @Valid在項(xiàng)目中的使用,需要的朋友可以參考下2018-05-05Java 獲取指定日期的實(shí)現(xiàn)方法總結(jié)
以下是對(duì)Java中獲取指定日期的實(shí)現(xiàn)方法進(jìn)行了歸納總結(jié),需要的朋友可以參考下2013-07-07如何解決websocket開啟多個(gè)頁(yè)面訪問同一個(gè)連接會(huì)失效的問題
使用WebSocket時(shí),若多個(gè)頁(yè)面訪問同一個(gè)WebSocket連接可能會(huì)導(dǎo)致連接失效,遇到這個(gè)問題時(shí),可以通過在SpringBoot中使用@ServerEndpoint注解并添加@Component來(lái)解決,出現(xiàn)連接錯(cuò)誤通常是因?yàn)閃ebSocket連接接收到的是一個(gè)GET請(qǐng)求2024-09-09Springboot實(shí)現(xiàn)全局自定義異常的方法詳解
這篇文章主要介紹了Springboot實(shí)現(xiàn)全局自定義異常的方法詳解,SpringBoot的項(xiàng)目已經(jīng)對(duì)有一定的異常處理了,但是對(duì)于我們開發(fā)者而言可能就不太合適了,因此我們需要對(duì)這些異常進(jìn)行統(tǒng)一的捕獲并處理,需要的朋友可以參考下2023-11-11java動(dòng)態(tài)代理實(shí)現(xiàn)代碼
這篇文章主要介紹了java 動(dòng)態(tài)代理的的相關(guān)資料,文中講解非常細(xì)致,代碼幫助大家更好的理解和學(xué)習(xí),感興趣的朋友可以了解下,希望能給你帶來(lái)幫助2021-07-07