ReentrantLock獲取鎖釋放鎖的流程示例分析
目的
- 了解ReentrantLock獲取鎖、釋放鎖的流程
代碼
package com.company.aqs; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /** * ReentrantLock使用案例——使用ReentrantLock加鎖 * @Author: Alan * @Date: 2022/11/20 01:38 */ public class ReentrantLockDemo { private static int sum=0; private static Lock lock=new ReentrantLock(); public static void main(String[] args) throws InterruptedException { for (int i = 0; i < 3; i++) { new Thread(()->{ // 獲取鎖 lock.lock(); try { for (int j = 0; j < 1000; j++) { sum++; } }finally { // 在finally代碼塊中釋放鎖 lock.unlock(); } }).start(); } // 保證所有線程執(zhí)行完畢 Thread.sleep(1000); System.out.println(sum); } }
這是一個(gè)使用ReentrantLock實(shí)現(xiàn)多線程求和的案例。代碼邏輯比較簡(jiǎn)單,外層循環(huán)開(kāi)啟了3個(gè)線程,然后每個(gè)線程內(nèi)多sum累加1000,最后輸出結(jié)果sum=1000。
獲取鎖流程
整個(gè)過(guò)程概括起來(lái)就做了兩件事兒
- 獲取鎖成功,執(zhí)行當(dāng)前線程內(nèi)的其他事情;
- 獲取鎖失敗,當(dāng)前線程加入同步隊(duì)列,同時(shí)阻塞當(dāng)前線程。
當(dāng)?shù)谝粋€(gè)線程(thead0)進(jìn)來(lái)的時(shí)候,通過(guò)CAS去修改state屬性為1,如果成功,通過(guò)setExclusiveOwnerThread()方法設(shè)置exclusiveOwnerThread為當(dāng)前線程
此時(shí),第二個(gè)線程(thead1)進(jìn)來(lái),再去通過(guò)CAS修改state屬性為1時(shí),便會(huì)失敗,此時(shí)進(jìn)入acquire()方法。最終會(huì)走到如下方法,首先通過(guò)tryAcquire()方法再次嘗試去獲取鎖。
tryAcquire()方法內(nèi)部還是會(huì)通過(guò)CAS去獲取鎖。此時(shí)鎖資源還被第二線程持有,因此會(huì)返回false?,F(xiàn)在接著看acquire()方法中的if判斷。
此時(shí),會(huì)進(jìn)行acquireQueued(addWaiter(Node.EXCLUSIVE), arg))判斷。這里需要執(zhí)行兩個(gè)方法addWaiter()和acquireQueued()。首先看addWaiter()方法。這個(gè)方法,我們需要關(guān)注以下四點(diǎn)。
- 首先會(huì)先構(gòu)造一個(gè)node節(jié)點(diǎn)(節(jié)點(diǎn)內(nèi)部細(xì)節(jié),可以看其構(gòu)造方法)。
- 如果tail(同步隊(duì)列尾節(jié)點(diǎn)指針)不為空,其實(shí)也就是同步隊(duì)列不為空,那么就把第1步構(gòu)建的節(jié)點(diǎn)通過(guò)尾插法加入隊(duì)列中,然后返回
如果同步隊(duì)列為空了,那么執(zhí)行enq()方法,這個(gè)方法為我們做了兩件事兒。
- 如果同步隊(duì)列為空,那么初始化隊(duì)列
- 隊(duì)列初始化完成后,將node節(jié)點(diǎn)入隊(duì)。
通過(guò)addWaiter()方法和enq()方法,我們也可以看出來(lái),AQS中的同步隊(duì)列是通過(guò)雙向鏈表來(lái)實(shí)現(xiàn)的,節(jié)點(diǎn)入隊(duì)和出隊(duì),需要修改兩個(gè)指針才行(prev和next)。
addWaiter()方法執(zhí)行執(zhí)行完畢后,我們通過(guò)下面這張圖大致看下此時(shí)同步隊(duì)列中的節(jié)點(diǎn)指向情況。此處,不太理解可以再回頭看看enq()方法的執(zhí)行流程。
addWaiter()方法執(zhí)行執(zhí)行完畢后,會(huì)返回入隊(duì)后的新節(jié)點(diǎn)。然后開(kāi)始執(zhí)行acquireQueued()方法。這個(gè)方法做了五件事兒。
- 獲取當(dāng)前節(jié)點(diǎn)的前驅(qū)節(jié)點(diǎn)p
- 如果p是頭節(jié)點(diǎn),那么再次嘗試去獲取鎖,獲取鎖成功,就可以跳出循環(huán)
- 獲取鎖失敗,通過(guò)shouldParkAfterFailedAcquire()去修改waitSatus為-1(為什么修改為-1,這里可以從AQS的源碼中找到原因,后續(xù)獲取鎖的流程也會(huì)遵從這個(gè)邏輯)
4. parkAndCheckInterrupt()方法會(huì)阻塞當(dāng)前線程,同時(shí),能夠返回當(dāng)前線程的中斷狀態(tài)(Thread.interrupted()會(huì)清除中斷標(biāo)記位)。
經(jīng)過(guò)上述的一通操作,我們可以知道,線程1沒(méi)有獲取到鎖,被加入到了同步隊(duì)列,并且還對(duì)其進(jìn)行了阻塞。概括下就是四個(gè)字“入隊(duì)”、“阻塞”。此時(shí)我們的同步隊(duì)列也變成如下所示。和上述執(zhí)行完addWaiter()方法相比,只是線程1節(jié)點(diǎn)的前驅(qū)節(jié)點(diǎn),waitStaus被設(shè)置成了-1。
釋放鎖流程
整個(gè)過(guò)程(只考慮釋放鎖成功)概括起來(lái)做了三件事兒
- 釋放鎖資源;
- 喚醒同步隊(duì)列中頭節(jié)點(diǎn)的后一個(gè)節(jié)點(diǎn)對(duì)應(yīng)的線程
- 步驟2被喚醒的該線程嘗試競(jìng)爭(zhēng)鎖,競(jìng)爭(zhēng)鎖成功,那么更新同步隊(duì)列(即頭節(jié)點(diǎn)出隊(duì))。
當(dāng)?shù)谝粋€(gè)線程(thread0)執(zhí)行完自己的業(yè)務(wù)流程后,就會(huì)釋放鎖。此時(shí),我們來(lái)看看釋放鎖的流程又是什么樣子的。調(diào)用unlock()方法可以對(duì)鎖進(jìn)行釋放,需要注意的時(shí),為了避免死鎖,需要將該方法的調(diào)用放在fiaally代碼塊中。
當(dāng)thread0去釋放鎖時(shí),會(huì)調(diào)用release()方法,該方法主要做了兩件事兒。
- 調(diào)用tryRelease()方法釋放鎖
其方法內(nèi)部,會(huì)將state設(shè)置為0,同時(shí)通過(guò)setExclusiveOwnerThread()方法設(shè)置exclusiveOwnerThread為當(dāng)null,代表此時(shí)鎖資源被釋放,沒(méi)有任何線程持有鎖資源
如果第一步返回true,即釋放鎖成功,那么開(kāi)始第二步。第二步首先獲取同步隊(duì)列的頭節(jié)點(diǎn),查看其waitStatus屬性。這里我們把剛才獲取鎖過(guò)后,同步隊(duì)列中的節(jié)點(diǎn)情況再放一下。此時(shí),我們的head節(jié)點(diǎn)的waitStatus為-1,因此會(huì)進(jìn)入unparkSuccessor()方法
unparkSuccessor()方法,主要做了以下三件事兒。注意第三步,根據(jù)我們當(dāng)前同步隊(duì)列的情況來(lái)看,LockSupport.unpark()方法會(huì)喚醒線程1。
當(dāng)執(zhí)行完unparkSuccessor()方法中的LockSupport.unpark()方法后,線程1(thread1)就會(huì)被喚醒了。線程1被喚醒過(guò)后,我們又來(lái)看看線程1的執(zhí)行情況。此時(shí)線程1,會(huì)繼續(xù)執(zhí)行acquireQueued()方法中的for循環(huán)(注意:這是一個(gè)死循環(huán)哦)。執(zhí)行順序和獲取鎖時(shí)是一致的,和獲取鎖有所不同的時(shí),此時(shí)執(zhí)行第2步獲取鎖是會(huì)成功的(因?yàn)閠hread0已經(jīng)釋放鎖了)。
獲取鎖成功后呢,會(huì)讓當(dāng)前同步隊(duì)列中的頭節(jié)點(diǎn)出隊(duì),此時(shí),同步隊(duì)列中的節(jié)點(diǎn)情況如下所示。
此時(shí),釋放鎖的邏輯就執(zhí)行完成了。歸納起來(lái)其實(shí)也很簡(jiǎn)單,首先釋放鎖資源,然后再喚醒同步隊(duì)列中頭節(jié)點(diǎn)的后一個(gè)節(jié)點(diǎn)對(duì)應(yīng)的線程,最后,更新同步隊(duì)列(出隊(duì))。
總結(jié)
以上便是ReentrantLock獲取鎖、釋放鎖的的大致流程。通過(guò)這篇文章,讀者對(duì)ReentrantLock的獲取鎖、釋放鎖過(guò)程有一個(gè)大致的了解了,細(xì)心的讀者可能會(huì)發(fā)現(xiàn),獲取鎖時(shí)acquireQueued()方法中有一個(gè)隊(duì)cancelAcquire()方法的調(diào)用邏輯,這里沒(méi)有詳細(xì)解釋,博主會(huì)在后面的文章中詳細(xì)來(lái)解釋這個(gè)方法的處理邏輯(flag先立下?。。。?。
以上就是ReentrantLock獲取鎖釋放鎖的流程示例分析的詳細(xì)內(nèi)容,更多關(guān)于ReentrantLock獲取鎖釋放鎖的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Maven分模塊開(kāi)發(fā)執(zhí)行指令失敗的問(wèn)題
Maven分模塊開(kāi)發(fā),行指令失敗,modules.module[3]‘ specifies duplicate child module maven_dao @ line 29, column 1的問(wèn)題,本文給大家分享解決方法,感興趣的朋友跟隨小編一起看看吧2020-09-09在Java開(kāi)發(fā)中無(wú)法繞開(kāi)的SpringBoot框架詳解
SpringBoot是一個(gè)基于Spring框架的快速開(kāi)發(fā)框架,它的出現(xiàn)極大地簡(jiǎn)化了Spring應(yīng)用的開(kāi)發(fā)流程,SpringBoot是一個(gè)快速開(kāi)發(fā)的框架,它提供了一種快速構(gòu)建應(yīng)用程序的方式,本文給大家介紹在Java開(kāi)發(fā)中無(wú)法繞開(kāi)的框架:SpringBoot,感興趣的朋友一起看看吧2023-09-09Java使用Optional實(shí)現(xiàn)優(yōu)雅避免空指針異常
空指針異常(NullPointerException)可以說(shuō)是Java程序員最容易遇到的問(wèn)題了。為了解決這個(gè)問(wèn)題,Java?8?版本中推出了?Optional?類,本文就來(lái)講講如何使用Optional實(shí)現(xiàn)優(yōu)雅避免空指針異常吧2023-03-03劍指Offer之Java算法習(xí)題精講二叉樹(shù)的構(gòu)造和遍歷
跟著思路走,之后從簡(jiǎn)單題入手,反復(fù)去看,做過(guò)之后可能會(huì)忘記,之后再做一次,記不住就反復(fù)做,反復(fù)尋求思路和規(guī)律,慢慢積累就會(huì)發(fā)現(xiàn)質(zhì)的變化2022-03-03Java經(jīng)緯度小數(shù)與度分秒相互轉(zhuǎn)換工具類示例詳解
這篇文章主要介紹了Java經(jīng)緯度小數(shù)與度分秒相互轉(zhuǎn)換工具類,本文通過(guò)示例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-07-07SpringBoot Admin健康檢查功能的實(shí)現(xiàn)
admin主要就是告訴運(yùn)維人員,服務(wù)出現(xiàn)異常,然后進(jìn)行通知(微信、郵件、短信、釘釘?shù)龋┛梢苑浅?焖偻ㄖ竭\(yùn)維人員,相當(dāng)報(bào)警功能,接下來(lái)通過(guò)本文給大家介紹SpringBoot Admin健康檢查的相關(guān)知識(shí),一起看看吧2021-06-06Springboot hibernate envers使用過(guò)程詳解
這篇文章主要介紹了Springboot hibernate envers使用過(guò)程詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-06-06Mybatis update數(shù)據(jù)庫(kù)死鎖之獲取數(shù)據(jù)庫(kù)連接池等待
這篇文章主要介紹了Mybatis update數(shù)據(jù)庫(kù)死鎖之獲取數(shù)據(jù)庫(kù)連接池等待的相關(guān)資料,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2016-07-07