Java并發(fā)編程Lock?Condition和ReentrantLock基本原理
Lock框架
Lock框架為java并發(fā)編程提供了除synchronized之外的另外一種選擇。synchronized是隱式實現(xiàn),底層封裝了對鎖資源的獲取和釋放的所有實現(xiàn)細(xì)節(jié),程序員不需要關(guān)心也沒有辦法關(guān)心這些細(xì)節(jié),使用起來非常方便也非常安全。
而Lock由java語言實現(xiàn),公開了鎖資源獲取和釋放的所有細(xì)節(jié),在資源鎖定過程中提供了更多選項,在獲取鎖資源后,可以通過Condition對象對鎖資源做細(xì)粒度的管理。
最關(guān)鍵的是Lock大量使用了CAS,充分利用“持有鎖的線程不會長時間占用鎖”這一假設(shè),有可能的情況下就盡量先自旋、后鎖定資源。所以多線程環(huán)境下Lock應(yīng)該比synchronized有更好的性能。
java線程池框架Executor中大量使用了基于Lock接口的ReentrantLock,掌握ReentrantLock是深入理解各種Executor(ThreadPoolExecutor、ScheduledThreadPoolExecutor等)以及各種阻塞隊列的必要前提。
Lock有獨占鎖、共享鎖的區(qū)別,獨占鎖是指某一線程獲取鎖資源后即獨占該鎖資源、其他線程只能等待,共享鎖是指多個線程能同時獲得鎖資源。
今天我們的研究對象是ReentrantLock,ReentrantLock是獨占鎖,主要研究內(nèi)容:
- ReentrantLock的基本概念
- 基礎(chǔ)數(shù)據(jù)機(jī)構(gòu):AQS,CLH隊列
- 公平鎖、非公平鎖
- Condition
- 沒有Condition參與的lock、unlock
- 有Condition參與的lock、unlock
ReentrantLock的基本概念
顧名思義,ReentrantLock是“可重入鎖”,意思是同一線程可以多次獲得鎖,n次獲得需要n次釋放才能最終釋放掉ReentrantLock。
ReentrantLock的基本原理:
- 與synchronized不同,ReentrantLock不存在“鎖對象”的概念,或者可以理解為鎖對象就是ReentrantLock對象本身
- ReentrantLock設(shè)置一個狀態(tài)值,通過對狀態(tài)值的原子操作實現(xiàn)對鎖資源的獲取和釋放,任何一個線程能獲取鎖資源的充分必要條件是ReentrantLock處于空閑狀態(tài),同理,任何一個線程獲得鎖資源后ReentrantLock即處于占用狀態(tài)
- ReentrantLock的兩個最基本的操作:lock和unlock,lock獲取鎖資源,unlock釋放鎖資源
- ReentrantLock維護(hù)一個CLH隊列,CLH隊列是一個先進(jìn)先出的雙向隊列
- ReentrantLock處于空閑狀態(tài)則lock調(diào)用立即返回,調(diào)用線程獲得鎖資源。否則,請求線程進(jìn)入CLH隊列排隊,等待被其他線程喚醒
- 獲得鎖資源的線程在業(yè)務(wù)執(zhí)行完成后調(diào)用unlock釋放鎖資源,之后以FIFO的原則喚醒最先進(jìn)入隊列排隊的線程
- 被喚醒的線程繼續(xù)執(zhí)行l(wèi)ock操作,節(jié)點從CLH隊列出隊,返回---意味著請求鎖資源的線程在等待后獲取鎖資源成功,繼續(xù)第6步的邏輯
以上是沒有Condition對象參與的ReentrantLock的獲取、釋放鎖資源的邏輯,相對比較簡單。
有Condition參與的時候,情況會稍微復(fù)雜一點:
- ReentrantLock對象可以通過new Condition()操作持有Condition對象,一個ReentrantLock可以持有多個Condition對象
- Condition維護(hù)一個Condition隊列
- Condition的常用的操作包括await、signal等,執(zhí)行操作的時候假設(shè)當(dāng)前線程已經(jīng)獲取到了ReentrantLock鎖資源
- await操作會釋放掉當(dāng)前線程已經(jīng)獲取到的ReentrantLock鎖資源、掛起當(dāng)前線程,并且將當(dāng)前線程加入Condition的隊列排隊等待被其他線程喚醒。比如DelayedWorkQueue的take方法中,如果當(dāng)前DelayedWorkQueue隊列空的話,則take線程加入到命名為available的Condition中排隊等候
- 當(dāng)相關(guān)操作可能導(dǎo)致Condition的條件滿足的時候,調(diào)用Condition的signal方法喚醒在Condition隊列中等待的線程。比如上例中DelayedWorkQueue的add方法完成之后,調(diào)用available的signal方法,喚醒在available隊列中排隊等候的線程。
- 線程被喚醒之后從Condition隊列出隊,進(jìn)ReentrantLock的CLH隊列排隊等待重新獲取鎖資源
Condition舉例:take方法中隊列空的話,掛起等待
Condition舉例:offer方法中寫入隊列后,喚醒等待的線程
對ReentrantLock應(yīng)該有一個基本的認(rèn)識了,如果只是想要對ReentrantLock做一個基本了解、能夠看懂ReentrantLock的應(yīng)用、而不是要從源碼角度做深入研究的話,個人認(rèn)為掌握上面這些基本原理應(yīng)該就夠了,保證能看懂阻塞隊列、線程池中的有關(guān)ReentrantLock的源碼邏輯了。
但是如果想要徹底搞清楚ReentrantLock到底是怎么實現(xiàn)以上邏輯的,就需要從源碼角度繼續(xù)做深入研究了。
ReentrantLock數(shù)據(jù)結(jié)構(gòu):AQS及CLH隊列
多個線程同時競爭ReentrantLock鎖資源的時候,只能有一個競爭獲勝的線程獲得鎖資源、其他線程就只能排隊等待。這個用來排隊的隊列就是CLH隊列,AQS(AbstractQueuedSynchronizer)是實現(xiàn)CLH隊列的虛擬類。
ReentrantLock有一個非常重要的屬性Sync,Sync是AQS的虛擬擴(kuò)展類,Sync有兩個實現(xiàn)類:NonfairSync和FairSync,類結(jié)構(gòu)如下:
NonfairSync和FairSync都是AQS的最終實現(xiàn),AQS虛擬類是一個標(biāo)準(zhǔn)模板,定義了Lock鎖的基本數(shù)據(jù)結(jié)構(gòu)(阻塞隊列)、并實現(xiàn)了Lock的絕大部分功能。
進(jìn)入隊列排隊的線程被封裝為Node,Node是AQS定義的內(nèi)部類,是我們學(xué)習(xí)AQS首先要掌握的內(nèi)容。
Node的重要屬性:
waitStatus:等待狀態(tài),Node就是用來排隊的,waitStatus就代表當(dāng)前節(jié)點的等待狀態(tài),有以下幾種等待狀態(tài):
- CANCELLED = 1:表示當(dāng)前等待線程已經(jīng)被calcel掉了
- SIGNAL = -1:表示該節(jié)點是在CLH隊列中排隊等待出隊
- CONDITION = -2:表示當(dāng)前節(jié)點是在Condition隊列中等待出隊
- PROPAGATE = -3:共享鎖會用到,暫不分析
prev:上一節(jié)點
next:雙向隊列嘛,當(dāng)然也要有下一節(jié)點
Thread thread:節(jié)點的主角,排隊線程
nextWaiter:Condition隊列專用,用來指向Condition隊列的下一節(jié)點
AQS的同步隊列(CLH)以及Condition隊列的節(jié)點都是用這個Node,所以Node類做了一部分針對兩者的兼容設(shè)計,比如nextWaiter是針對Condtion隊列的下一節(jié)點,next是針對CLH的下一節(jié)點。
AQS重要屬性
state:鎖狀態(tài),通過對state的原子操作實現(xiàn)對鎖資源的控制:某一線程通過原子操作成功將state從空閑修改為占用則意味著當(dāng)前線程成功獲得了鎖資源。無法獲得鎖資源的線程則封裝為Node節(jié)點進(jìn)入隊列排隊等待。
head:首節(jié)點,頭節(jié)點
tail:尾結(jié)點
通過head節(jié)點、tail節(jié)點,以及每個節(jié)點的prev、next,AQS實現(xiàn)了一個雙向隊列。
公平鎖和非公平鎖
所謂的公平鎖和非公平鎖就是由Sync屬性決定的:當(dāng)Sync創(chuàng)建為NonfairSync的時候,就是非公平的ReentrantLock,否則就是公平的ReentrantLock。
使用無參構(gòu)造器創(chuàng)建的是非公平ReentrantLock,有參構(gòu)造器ReentrantLock(boolean fair)可以通過參數(shù)指定創(chuàng)建公平還是非公平鎖。
公平鎖在線程請求鎖資源的時候會檢查CLH隊列,隊列不空的話首先進(jìn)入隊列排隊,先提出申請的線程會優(yōu)先獲得鎖資源,因此是“公平”的鎖。
非公平鎖在線程請求鎖資源的時候不會檢查CLH隊列,直接嘗試獲得鎖資源,獲取失敗后才進(jìn)入隊列排隊。所以請求線程會得到比隊列中的線程更高的優(yōu)先級,對于隊列中排隊的線程來說是不公平的,所以叫非公平鎖。
Condition
Condition提供await和signal(以及他們的變種)方法為ReentrantLock鎖資源提供更多選擇:當(dāng)前線程獲取到ReentrantLock鎖資源后,可以通過Condition對象的await方法掛起當(dāng)前線程直到其他線程通過該對象的signal方法喚醒。
一個ReentrantLock可以創(chuàng)建多個Condition對象,每一個Condition對象都是獨立的、互不影響。ReentrantLock好比是一條街上的黑社會老大,黑社會老大首先要把這條街拿下,也就是獲得ReentrantLock鎖資源。之后的每一個Condition好比是這條街道上的飯店A、小賣店B、公共衛(wèi)生間C,分別對應(yīng)ConditionObjectA、ConditionObjectB、ConditionObjectC,得到黑社會老大允許后你就可以隨意進(jìn)出飯店吃飯了,但是如果飯店客滿了,就必須通過調(diào)用ConditionObjectA的await方法進(jìn)入到ConditionObjectA的隊列中排隊等待(當(dāng)前線程封裝為AQS中的Node進(jìn)入隊列(假設(shè)叫NodeA),當(dāng)前線程A掛起),此時黑社會老大需要交出對整條街的鎖權(quán)限(貌似不太合理...),此后飯店A有人吃完了要離店,就會通過ConditionObjectA的signal方法通知正在隊列中排隊等候的NodeA,于是NodeA從ConditionObjectA隊列中出來,到ReentrantLock的CLH隊列中排隊、等待重新獲取ReentrantLock鎖資源之后再喚醒線程A。這個過程中如果有其他人(其他線程)要進(jìn)入小賣店B,需要進(jìn)行操作的就是小賣店對應(yīng)的ConditionObjectB,和飯店對應(yīng)的ConditionObjectA沒有任何關(guān)系。
小結(jié)
發(fā)現(xiàn)開篇定下的內(nèi)容太多了,篇幅所限,后面的“沒有Condition參與的lock、unlock”以及“有Condition參與的lock、unlock”,基本就是上述邏輯的源碼分析,放在下一篇。
以上就是Java并發(fā)編程Lock Condition和ReentrantLock基本原理的詳細(xì)內(nèi)容,更多關(guān)于Java并發(fā)編程的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
java程序員必須要學(xué)會的linux命令總結(jié)(推薦)
下面小編就為大家分享一篇java程序員必須要學(xué)會的linux命令總結(jié)(推薦)。具有很好的參考價值。希望對大家有所幫助。一起跟隨小編過來看看吧2017-11-11Java中的讀寫鎖ReentrantReadWriteLock源碼分析
這篇文章主要介紹了Java中的讀寫鎖ReentrantReadWriteLock源碼分析,ReentrantReadWriteLock 分為讀鎖和寫鎖兩個實例,讀鎖是共享鎖,可被多個線程同時使用,寫鎖是獨占鎖,持有寫鎖的線程可以繼續(xù)獲取讀鎖,反之不行,需要的朋友可以參考下2023-12-12Java中print、printf、println的區(qū)別
這篇文章主要介紹了Java中print、printf、println的區(qū)別的相關(guān)資料,需要的朋友可以參考下2023-03-03springboot中配置好登錄攔截后,swagger訪問不了問題
這篇文章主要介紹了springboot中配置好登錄攔截后,swagger訪問不了問題及解決,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2023-12-12Springboot基于enable模塊驅(qū)動的實現(xiàn)
這篇文章主要介紹了Springboot基于enable模塊驅(qū)動的實現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-08-08SpringCloud基于Feign實現(xiàn)遠(yuǎn)程調(diào)用的問題小結(jié)
這篇文章主要介紹了SpringCloud基于Feign遠(yuǎn)程調(diào)用,通過使用 Feign 的方式,我們可以更加優(yōu)雅地進(jìn)行多參數(shù)的遠(yuǎn)程調(diào)用,避免了手動拼接URL或構(gòu)建復(fù)雜的請求體,需要的朋友可以參考下2024-02-02Spring Data JPA結(jié)合Mybatis進(jìn)行分頁查詢的實現(xiàn)
本文主要介紹了Spring Data JPA結(jié)合Mybatis進(jìn)行分頁查詢的實現(xiàn)2024-03-03Java8通過Function獲取字段名的方法(獲取實體類的字段名稱)
Java8通過Function獲取字段名。不用再硬編碼,效果類似于mybatis-plus的LambdaQueryWrapper,對Java8通過Function獲取字段名相關(guān)知識感興趣的朋友一起看看吧2021-09-09