欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

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

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

前言

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

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

加鎖

我們先來看看加鎖的過程,先看源碼,然后模擬兩個線程來加鎖的過程。

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

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

重寫了它的lock加鎖方法,在實現(xiàn)中因為是非公平的,所以一進來會先通過cas嘗試將AQS類的state參數(shù)改為1,直接嘗試加鎖。如果嘗試加鎖失敗會調(diào)用AQS的acquire方法繼續(xù)嘗試加鎖。

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

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

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

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

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

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

放入鏈表中之后如圖。

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

這個方法一進來也會嘗試將當前節(jié)點去加鎖,然后如果加鎖成功就將當前節(jié)點設(shè)置為頭節(jié)點,最后將當前線程中斷,等待喚醒。

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

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

接下來就會調(diào)用parkAndCheckInterrupt

,這個方法內(nèi)部調(diào)用LockSupport.park方法,此時當前線程就會休眠。

到這一步線程2由于沒有獲取到鎖,就會在這里休眠等待被喚醒。

來總結(jié)一下加鎖的過程。

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

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

釋放鎖

ok,說完加鎖的過程之后,我們來看看釋放鎖干了什么。

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

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

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

然后就會走接下來的代碼。

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

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

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

我再次貼出這段代碼。

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

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

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

總結(jié)

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

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

相關(guān)文章

最新評論