亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

教你Java中的Lock鎖底層AQS到底是如何實(shí)現(xiàn)的

 更新時(shí)間:2022年05月27日 14:16:16   作者:三友的java日記  
本文是基于ReentrantLock來(lái)講解,ReentrantLock加鎖只是對(duì)AQS的api的調(diào)用,底層的鎖的狀態(tài)(state)和其他線程等待(Node雙向鏈表)的過(guò)程其實(shí)是由AQS來(lái)維護(hù)的,對(duì)Java?Lock鎖AQS實(shí)現(xiàn)過(guò)程感興趣的朋友一起看看吧

前言

相信大家對(duì)Java中的Lock鎖應(yīng)該不會(huì)陌生,比如ReentrantLock,鎖主要是用來(lái)解決解決多線程運(yùn)行訪問(wèn)共享資源時(shí)的線程安全問(wèn)題。那你是不是很好奇,這些Lock鎖api是如何實(shí)現(xiàn)的呢?本文就是來(lái)探討一下這些Lock鎖底層的AQS(AbstractQueuedSynchronizer)到底是如何實(shí)現(xiàn)的。

本文是基于ReentrantLock來(lái)講解,ReentrantLock加鎖只是對(duì)AQS的api的調(diào)用,底層的鎖的狀態(tài)(state)和其他線程等待(Node雙向鏈表)的過(guò)程其實(shí)是由AQS來(lái)維護(hù)的

加鎖

我們先來(lái)看看加鎖的過(guò)程,先看源碼,然后模擬兩個(gè)線程來(lái)加鎖的過(guò)程。

上圖是ReentrantLock的部分實(shí)現(xiàn)。里面有一個(gè)Sync的內(nèi)部類的實(shí)例變量,這個(gè)Sync內(nèi)部類繼承自AQS,Sync子類就包括公平鎖和非公平鎖的實(shí)現(xiàn)。說(shuō)白了其實(shí)ReentrantLock是通過(guò)Sync的子類來(lái)實(shí)現(xiàn)加鎖。

我們就來(lái)看一下Sync的非公平鎖的實(shí)現(xiàn)NonfairSync。

重寫了它的lock加鎖方法,在實(shí)現(xiàn)中因?yàn)槭欠枪降?,所以一進(jìn)來(lái)會(huì)先通過(guò)cas嘗試將AQS類的state參數(shù)改為1,直接嘗試加鎖。如果嘗試加鎖失敗會(huì)調(diào)用AQS的acquire方法繼續(xù)嘗試加鎖。

假設(shè)這里有個(gè)線程1先來(lái)調(diào)用lock方法,那么此時(shí)沒(méi)有人加鎖,那么就通過(guò)CAS操作,將AQS中的state中的變量由0改為1,代表有人來(lái)加鎖,然后將加鎖的線程設(shè)置為自己如圖。

那么此時(shí)有另一個(gè)線程2來(lái)加鎖,發(fā)現(xiàn)通過(guò)CAS操作會(huì)失敗,因?yàn)閟tate已經(jīng)被設(shè)置為1了,線程線程2就會(huì)設(shè)置失敗,那么此時(shí)就會(huì)走else,調(diào)用AQS的acquire方法繼續(xù)嘗試加鎖。

進(jìn)入到acquire會(huì)先調(diào)用tryAcquire再次嘗試加鎖,而這個(gè)tryAcquire方法AQS其實(shí)是沒(méi)有什么實(shí)現(xiàn)的,會(huì)調(diào)用到NonfairSync里面的tryAcquire,而tryAcquire實(shí)際會(huì)調(diào)用到Sync內(nèi)部類里面的nonfairTryAcquire非公平嘗試加鎖方法。

先獲取鎖的狀態(tài),判斷鎖的狀態(tài)是不是等于0,等于0說(shuō)明沒(méi)人加鎖,可以嘗試去加,如果被加鎖了,就會(huì)走else if,else if會(huì)判斷加鎖的線程是不是當(dāng)前線程,是的話就給state 加 1,代表當(dāng)前線程加了2次鎖,就是可重入鎖的意思(所謂的可重入就是代表一個(gè)線程可以多次獲取到鎖,只是將state 設(shè)置為多次,當(dāng)線程多次釋放鎖之后,將state 設(shè)置為0才代表當(dāng)前線程完全釋放了鎖)。

這里所有的條件假設(shè)都不成立。也就是線程2嘗試加鎖的時(shí)候,線程1并沒(méi)有釋放鎖,那么這個(gè)方法就會(huì)返回false。

接下來(lái)就會(huì)走到addWaiter方法,這個(gè)方法很重要,就是將當(dāng)前線程封裝成一個(gè)Node,然后將這個(gè)Node放入雙向鏈表中。addWaiter先根據(jù)指定模式創(chuàng)建指定的node節(jié)點(diǎn),因?yàn)镽eentrantLock是獨(dú)占模式,所以傳進(jìn)去的EXCLUSIVE,這里通過(guò)當(dāng)前線程和模式傳入,初始化一個(gè)雙向node節(jié)點(diǎn),獲取最后一個(gè)節(jié)點(diǎn),根據(jù)最后一個(gè)節(jié)點(diǎn)是否存在來(lái)操作當(dāng)前節(jié)點(diǎn)的父級(jí)。如果尾節(jié)點(diǎn)不存在會(huì)去調(diào)用enq去初始化

放入鏈表中之后如圖。

然后調(diào)用acquireQueued方法

這個(gè)方法一進(jìn)來(lái)也會(huì)嘗試將當(dāng)前節(jié)點(diǎn)去加鎖,然后如果加鎖成功就將當(dāng)前節(jié)點(diǎn)設(shè)置為頭節(jié)點(diǎn),最后將當(dāng)前線程中斷,等待喚醒。

線程2進(jìn)來(lái)的時(shí)候,剛好線程2的前一個(gè)節(jié)點(diǎn)是頭節(jié)點(diǎn),但是不巧的是調(diào)用tryAcquire方法,還是失敗,那么此時(shí)就會(huì)走shouldParkAfterFailedAcquire方法,這個(gè)方法是在線程休眠之前調(diào)用的,很重要,我們來(lái)看看干了什么事。

判斷當(dāng)前節(jié)點(diǎn)的父級(jí)節(jié)點(diǎn)的狀態(tài),如果父級(jí)狀態(tài)是-1,則代表當(dāng)前線程可以被喚醒了。如果父級(jí)的狀態(tài)為取消狀態(tài)(什么叫非取消狀態(tài),就是tryLock方法等待了一些時(shí)間沒(méi)獲取到鎖的線程就處于取消狀態(tài))就跳過(guò)父級(jí),尋找下一個(gè)可以被喚醒的父級(jí),然后綁定上節(jié)點(diǎn)關(guān)系,最后將父級(jí)的狀態(tài)更改為-1。也就說(shuō),線程(Node)加入隊(duì)列之后,如果沒(méi)有獲取到鎖,在睡眠之前,會(huì)將當(dāng)前節(jié)點(diǎn)的前一個(gè)節(jié)點(diǎn)設(shè)置為非取消狀態(tài)的節(jié)點(diǎn),然后將前一個(gè)節(jié)點(diǎn)的waitStatus設(shè)置為-1,代表前一個(gè)節(jié)點(diǎn)在釋放鎖的時(shí)候需要喚醒下一個(gè)節(jié)點(diǎn)。這一步驟主要是防止當(dāng)前休眠的線程無(wú)法被喚醒。這一切設(shè)置成功之后,就會(huì)返回true。

接下來(lái)就會(huì)調(diào)用parkAndCheckInterrupt

,這個(gè)方法內(nèi)部調(diào)用LockSupport.park方法,此時(shí)當(dāng)前線程就會(huì)休眠。

到這一步線程2由于沒(méi)有獲取到鎖,就會(huì)在這里休眠等待被喚醒。

來(lái)總結(jié)一下加鎖的過(guò)程。

線程1先過(guò)來(lái),發(fā)現(xiàn)沒(méi)人加鎖,那么此時(shí)就會(huì)加上鎖。此時(shí)線程2過(guò)來(lái),在線程2加鎖的過(guò)程中,線程1始終沒(méi)有釋放鎖,那么線程2就不會(huì)加鎖成功(如果在線程2加鎖的過(guò)程中線程1始終釋放鎖,那么線程2就會(huì)加鎖成功),線程2沒(méi)有加鎖成功,就會(huì)將自己當(dāng)前線程加入等待隊(duì)列中(如果沒(méi)有隊(duì)列就先初始化一個(gè)),然后設(shè)置前一個(gè)節(jié)點(diǎn)的狀態(tài),最后通過(guò)LockSupport.park方法,將自己這個(gè)線程休眠。

如果后面還有線程3,線程4等等諸多的先過(guò)來(lái),那么這些線程都會(huì)按照前面線程2的步驟,將自己插入鏈表后面再休眠。

釋放鎖

ok,說(shuō)完加鎖的過(guò)程之后,我們來(lái)看看釋放鎖干了什么。

ReentrantLock的unlock其實(shí)是調(diào)用AQS的release方法,我們直接進(jìn)入release方法,看看是如何實(shí)現(xiàn)的

進(jìn)入tryRelease方法,看一下Sync的實(shí)現(xiàn)

其實(shí)很簡(jiǎn)單,就是判斷鎖的狀態(tài),也就是加了幾次鎖,然后減去釋放的,最后判斷釋放之后,鎖的狀態(tài)是不是0(因?yàn)榭赡芫€程加了多次鎖,所以得判斷一下),是的話說(shuō)明當(dāng)前這個(gè)鎖已經(jīng)釋放完了,然后將占有鎖的線程設(shè)置為null,然后返回true,

然后就會(huì)走接下來(lái)的代碼。

就是判斷當(dāng)前鏈表頭節(jié)點(diǎn)是不是需要喚醒隊(duì)列中的線程。如果有鏈表的話,頭結(jié)點(diǎn)的waitStatus肯定不是0,因?yàn)榫€程休眠之前,會(huì)將前一個(gè)節(jié)點(diǎn)的狀態(tài)設(shè)置為-1,上面加鎖的過(guò)程中有提到過(guò)。

接下來(lái)就會(huì)走unparkSuccessor方法,successor代表繼承者的意思,見(jiàn)名知意,這個(gè)方法其實(shí)就會(huì)喚醒當(dāng)前線程中離頭節(jié)點(diǎn)最近的沒(méi)有狀態(tài)為非取消的線程。然后調(diào)用LockSupport.unpark,喚醒等待的線

然后線程就會(huì)從阻塞的那里蘇醒過(guò)來(lái),繼續(xù)嘗試獲取鎖。

我再次貼出這段代碼。

獲取到鎖之后,就將頭節(jié)點(diǎn)設(shè)置成自己。

對(duì)應(yīng)我們的例子,就是線程1釋放鎖之后,就會(huì)喚醒在隊(duì)列中線程2,先成2獲取到鎖之后,就會(huì)將自己前一個(gè)節(jié)點(diǎn)(也就是頭節(jié)點(diǎn))從鏈表中移除,將自己設(shè)置成頭節(jié)點(diǎn)。該方法就會(huì)跳出死循環(huán)。

到這里,釋放鎖的過(guò)程就講完了,其實(shí)很簡(jiǎn)單,就是當(dāng)線程完完全全釋放了鎖,會(huì)喚醒當(dāng)前鏈表中的沒(méi)有取消的,離頭結(jié)點(diǎn)最近的節(jié)點(diǎn)(一般就是鏈表中的第二個(gè)節(jié)點(diǎn)),然后被喚醒的節(jié)點(diǎn)就會(huì)獲取到鎖,將頭節(jié)點(diǎn)設(shè)置為自己。

總結(jié)

相信看完這篇文章,大家對(duì)AQS的底層有了更深層次的了解。AQS其實(shí)就是內(nèi)部維護(hù)一個(gè)鎖的狀態(tài)變量state和一個(gè)雙向鏈表,加鎖成功就將state的值加1,加鎖失敗就將自己當(dāng)前線程放入鏈表的尾部,然后休眠,等待其他線程完完全全釋放鎖之后將自己?jiǎn)拘?,喚醒之后?huì)嘗試加鎖,加鎖成功就會(huì)執(zhí)行業(yè)務(wù)代碼了。

到此這篇關(guān)于教你Java中的Lock鎖底層AQS到底是如何實(shí)現(xiàn)的的文章就介紹到這了,更多相關(guān)Java Lock鎖AQS實(shí)現(xiàn)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評(píng)論