最美情侣中文字幕电影,在线麻豆精品传媒,在线网站高清黄,久久黄色视频

歡迎光臨散文網(wǎng) 會員登陸 & 注冊

面試官:不會AQS?那你簡歷上寫什么并發(fā)編程

2023-07-10 13:44 作者:職場小白-小岳  | 我要投稿

關(guān)于AQS(AbstractQueuedSynchronizer),其實平時我們使用的最多的就是ReentrantLock,我們先看看ReentrantLock到底是個什么東東

原來ReentrantLock 是Lock的實現(xiàn)類之一,而且它還有一個非常重要的屬性Sync,這個類可是關(guān)鍵哦。當(dāng)我們調(diào)用ReentrantLock的Lock()方法加鎖的時候發(fā)生什么?進(jìn)入源碼一看,原來它調(diào)用了sync的lock()方法

那這個sync又是什么東東呢?我們再點進(jìn)源碼一看,原來這個就是我們大名鼎鼎的AQS類的子類之一啊??!里面也有l(wèi)ock()方法

好啦,這里我們就看到了我們經(jīng)常說的ReentrantLock原來是借助AQS實現(xiàn)的鎖啊,那么接下來我們就要進(jìn)入AQS的源碼去看看啦,沖沖沖


這里的lock是一個抽象方法,它有不同的實現(xiàn)類

盲猜,也是就我們所說的公平鎖和非公平鎖了,畢竟面試官問你它的優(yōu)勢,你肯定會說它既能實現(xiàn)公平鎖又能實現(xiàn)非公平鎖


果不其然

我們先來看看公平鎖的實現(xiàn)原理,公平鎖的實現(xiàn)里面調(diào)用acquire()方法,并且傳入了參數(shù)1

繼續(xù)刨根,這里的acquire()的大概邏輯就出來了。我們先說一下大概的邏輯,再往里面深扒。首先這里tryAcquire方法是嘗試獲取鎖,如果失敗了addWaiter方法是進(jìn)入一個隊列,這個隊列是雙向鏈表維護(hù)的;進(jìn)入隊列之后,這個線程還是不得死心,它還要再掙扎一下,也就是我們通常所說的自旋(2次),于是它繼續(xù)調(diào)用acquireQueued方法,最后實在拿不到鎖了,它就睡眠自己了。

這就是AQS去加鎖的一個大致流程。看到這里你可能還不覺得Doug Lea有多牛逼,但是等會你就嘆服


我們先去看一下tryAcquire方法,嘗試獲取一下鎖。

下面我們就一起暢游吧,首先拿到當(dāng)前運(yùn)行的線程current ,再通過getState方法獲取鎖的狀態(tài),這里的state屬性表示鎖的狀態(tài),默認(rèn)為0表示無鎖。同時為了保證線程之間的可見性,使用volatile關(guān)鍵字修飾private volatile int state;假如此時狀態(tài)為0,進(jìn)入第一個if判斷,這里通過hasQueuedPredecessors方法判斷自己是否需要入隊。我們進(jìn)入這個方法里面看看

這個方法里面有兩個Node節(jié)點,分別是頭尾節(jié)點,初始值都為null,此時我們是第一個線程進(jìn)來拿鎖,所以隊列并沒有被初始化,因此h != tfalse,后面的&&就自然短路了。整個方法返回false。
好,那就繼續(xù)回到咱們上面的嘗試獲取鎖的第一個if代碼塊

由于hasQueuedPredecessors方法返回false,前面加了!,所以整個為true。繼續(xù)判斷&&后面的代碼,這里就有了CAS原子操作了。compareAndSetState(0, acquires)是以CAS的方式將鎖的狀態(tài)從0修改為1。假如線程拿到了鎖,返回true,進(jìn)入下面的代碼塊setExclusiveOwnerThread(current),這個方法就是為了把當(dāng)前線程設(shè)置為拿到鎖的線程,為啥呢?后面在判斷可重入鎖的時候用。最后整個tryAcquire方法返回了true。

該線程拿到了鎖,進(jìn)入了我們的業(yè)務(wù)邏輯代碼開始執(zhí)行…

這里只是模擬第一個線程來拿鎖的情況,怎么樣,挺得住嗎


接下來咱們開始看看面試官都喜歡問的可重入鎖,其實很簡單,也在咱們的tryAcquire方法中。假如拿到鎖的線程在業(yè)務(wù)邏輯代碼中需要再次拿到鎖,依舊會走上面的流程來到咱們的tryAcquire中,只不過此時的state已經(jīng)被它自己修改為了1,所以進(jìn)入這個代碼塊

首先判斷當(dāng)前線程是不是持有鎖的線程啊?毋庸置疑,肯定是的,那就把鎖的狀態(tài)+1就完了,然后返回true,業(yè)務(wù)邏輯代碼正常執(zhí)行。怎么樣?是不是很簡單。原來可重入的意思就是線程可以再次拿到鎖,只不過把鎖的狀態(tài)值增加1而已。

上面的過程你是不是感覺很簡單嘛?確實,假如面試管問你AQS,你首先告訴他,多線程在交替執(zhí)行的時候,AQS的隊列并沒有初始化,也沒什么卵用。 他可能會覺得,這小子可能真看過源碼

接下來咱們講點有趣的。多線程不再交替執(zhí)行,也就是說存在競爭了,那咋辦?Doug Lea都給你安排的明明白白的,不慌。

第一個線程在執(zhí)行,第二個線程來了,它也會進(jìn)入tryAcquire方法,然后它發(fā)現(xiàn)鎖的狀態(tài)不為0,同時自己也不是持有鎖的線程,那它只能可憐的拿到最后一行代碼,返回false。接下來主場戲就要開始了,真的精彩,有尿你都憋?。。。?/p>

還記得acquire方法嗎

此時tryAcquire返回false,前面加了!,所以為true,于是代碼繼續(xù)往后判斷,成功進(jìn)入addWaiter方法,大膽的點進(jìn)去

首先把當(dāng)前線程封裝為一個Node節(jié)點,然后判斷tail節(jié)點是否為null,這里首尾節(jié)點都沒有初始化,肯定為null啊,所以直接走enq(node),再點進(jìn)去

這里面是一個自旋的方法,第一次自旋的時候,先通過CAS的方式創(chuàng)建一個空節(jié)點,并讓head指向空節(jié)點,之所以叫空節(jié)點,是因為里面的thread為null。然后尾節(jié)點也指向這個空節(jié)點。然后再自旋一次,通過CAS的方式讓當(dāng)先線程所在的節(jié)點掛到空節(jié)點后面。

到此為止,第二個線程做了什么事情呢?首先去拿鎖,拿不到;然后入隊列,發(fā)現(xiàn)隊列未初始化,自己去做了初始化的工作,并且把自己掛到了虛擬頭節(jié)點的后面。接下來他該干嘛了呢?他要準(zhǔn)備睡眠自己了,不過,他可不得這么輕易睡覺,你上床睡覺不得在被窩里掙扎一下

話不多說,來人,上源碼
進(jìn)入acquireQueued方法啦,其實就是詢問自己是不是真的要睡眠了

第一感覺就是又在自旋對不對,是的AQS就是自旋+CAS+雙向隊列+park。第一次自旋,先拿到當(dāng)前線程所在Node節(jié)點的前一個節(jié)點final Node p = node.predecessor(),然后判斷前一個節(jié)點是不是虛擬頭節(jié)點,如果是的話,他就去嘗試獲取鎖(第一次掙扎),這里我們假設(shè)他獲取鎖失敗。于是進(jìn)入下面的方法shouldParkAfterFailedAcquire(p, node),這個方法是在干嘛呢?大膽點進(jìn)去

不要慌,代碼很長,邏輯很短,全是注釋,咱們一步一步來分析

首先,會拿到前一個節(jié)點的waitStatus,咱們簡稱ws。然后就是判斷ws的值,ws默認(rèn)為0,所以第一次自旋進(jìn)來,當(dāng)前線程會把他前面的節(jié)點的ws狀態(tài)使用CAS的方式修改為-1,就是這句代碼compareAndSetWaitStatus(pred, ws, Node.SIGNAL),然后返回一個大大的false。由于返回false,所以

不會繼續(xù)往下執(zhí)行;代碼進(jìn)入第二次自旋,第二次自旋我們又假設(shè)沒有獲取到鎖,(不是線程二不給力,只是后面再專門講在競爭情況下拿到了鎖該怎么維護(hù)這個雙向隊列,忍耐一下)所以再次進(jìn)入shouldParkAfterFailedAcquire方法,由于第一次自旋的時候ws已經(jīng)被修改為-1了,所以這次直接返回true。然后就執(zhí)行后面的if語句后面的代碼塊了

也就是說park當(dāng)前線程,是線程睡眠。
到這里我們發(fā)現(xiàn),第二個線程在睡眠之前通過兩次自旋又做了兩件事情,第一件:把自己前一個節(jié)點ws修改為-1,第二件就是自己睡眠。

其實為啥也這樣設(shè)計呢? 我自己感覺哈,給head虛擬節(jié)點兩次掙扎的機(jī)會,就是為了進(jìn)來不讓線程park,這會設(shè)計用戶態(tài)和內(nèi)核態(tài)的切換,十分消耗性能。我們一直抱有僥幸心里,總覺得可能我剛?cè)腙?,前面一個線程就釋放鎖了呢。那我是不是可以不用睡眠,直接執(zhí)行呢?這種情況確實可能存在。但是我們是公平鎖,只有head節(jié)點的后面第一個節(jié)點可以去嘗試獲取鎖,其它后面進(jìn)來的節(jié)點你就早點洗洗睡吧,你前面的都還是排隊呢,你猴急啥。選擇兩次自旋也是出于對性能的考慮,如果自旋太多,會十分影響CPU的性能。這個設(shè)計真的非常巧妙。

后面再來第三個、第四個線程等等都是這樣自旋兩次,唯一不一樣的就是在acquireQueued方法中,因為自己的上一個節(jié)點不是head虛擬節(jié)點,所以不會執(zhí)行tryAcquire方法,但是依舊會修改前面節(jié)點的ws,然后睡眠。這里面我們模擬了線程交替執(zhí)行獲取鎖,模擬了第一個線程占用鎖,后面所有的線程拿不到鎖入隊列的過程,就問你Doug Lea強(qiáng)不強(qiáng)???

聽到這里,可能有小伙伴要為線程二正名了,為什么他就那么苦逼,每次拿不到鎖。好的,那這次咱們就讓他硬氣一回,讓他拿到鎖,當(dāng)隊列中的Node拿到鎖了又該如何去維護(hù)這個鏈表呢?
愣著干嘛??上菜了
假如線程二在自旋的過程中拿到鎖了。開心,終于輪到我執(zhí)行了

進(jìn)入到if代碼塊中,首先是setHead方法,咱們?nèi)タ纯此闪耸裁词虑?/p>

這個方法就是把head節(jié)點指向獲取到鎖的節(jié)點,然后把節(jié)點中的thread置為null,然后把他的前面階段斷開(這個以前的head虛擬節(jié)點就沒有了引用,就會被GC回收了)。

看懂了嗎?巧妙嗎?折服了嗎?

拿到鎖的線程去執(zhí)行了,然后他所處的Node變?yōu)榱薶ead虛擬節(jié)點,簡直太優(yōu)秀了?。?!

到此,咱們公平鎖的基本上就講完了,這才講了一半,哈哈哈,不過相信你已經(jīng)可以自己去分析非公平鎖的源碼了,一步一步進(jìn)去看,肯定可以看明白的

其實非公平鎖也不難,我們大概看一下

這是他的lock方法,每個線程一上來,不管隊列中有沒有人派對,自己先去搶占鎖,搶不到再說

后面有時間再閱讀解鎖和喚醒的源碼。這是自己看了源碼的一些心得體會,自己記錄看一下,同時也希望幫助到正在啃源碼的你。有錯的地方可以在評論區(qū)留言交流






面試官:不會AQS?那你簡歷上寫什么并發(fā)編程的評論 (共 條)

分享到微博請遵守國家法律
会昌县| 小金县| 农安县| 巴彦县| 建水县| 德格县| 安多县| 依兰县| 阜城县| 东阳市| 太仓市| 伊宁县| 神农架林区| 长岭县| 同江市| 湛江市| 南阳市| 松江区| 蒲江县| 沂南县| 广平县| 江城| 体育| 甘肃省| 宁乡县| 本溪| 革吉县| 大连市| 贺兰县| 惠安县| 金昌市| 深水埗区| 五峰| 牙克石市| 屏东县| 德令哈市| 探索| 会宁县| 涟源市| 瑞丽市| 宣威市|