亚洲中字慕日产2020,大陆极品少妇内射AAAAAA,无码av大香线蕉伊人久久,久久精品国产亚洲av麻豆网站

資訊專(zhuān)欄INFORMATION COLUMN

Java多線程進(jìn)階(七)—— J.U.C之locks框架:AQS獨(dú)占功能剖析(2)

JayChen / 3578人閱讀

摘要:開(kāi)始獲取鎖終于輪到出場(chǎng)了,的調(diào)用過(guò)程和完全一樣,同樣拿不到鎖,然后加入到等待隊(duì)列隊(duì)尾然后,在阻塞前需要把前驅(qū)結(jié)點(diǎn)的狀態(tài)置為,以確保將來(lái)可以被喚醒至此,的執(zhí)行也暫告一段落了安心得在等待隊(duì)列中睡覺(jué)。

本文首發(fā)于一世流云的專(zhuān)欄:https://segmentfault.com/blog...
一、本章概述

本章以ReentrantLock的調(diào)用為例,說(shuō)明AbstractQueuedSynchronizer提供的獨(dú)占功能。
本章結(jié)構(gòu)如下:

以ReentrantLock的公平策略為例,分析AbstractQueuedSynchronizer的獨(dú)占功能

以ReentrantLock的非公平策略為例,分析AbstractQueuedSynchronizer的獨(dú)占功能

分析AbstractQueuedSynchronizer的鎖中斷、限時(shí)等待等功能

二、ReentrantLock的公平策略原理

本節(jié)對(duì)ReentrantLock公平策略的分析基于以下示例:

假設(shè)現(xiàn)在有3個(gè)線程:ThreadA、ThreadB、ThreadC,一個(gè)公平的獨(dú)占鎖,3個(gè)線程會(huì)依次嘗試去獲取鎖:ReentrantLock lock=new ReentrantLock(true);

線程的操作時(shí)序如下:

//ThreadA    lock

//ThreadB    lock

//ThreadC    lock

//ThreadA    release

//ThreadB    release

//ThreadC    release
2.1 ThreadA首先獲取到鎖

ThreadA首先調(diào)用ReentrantLock的lock方法,我們看下該方法的內(nèi)部:

最終其實(shí)調(diào)用了FairSync的lock方法:

acquire方法來(lái)自AQS:

其中tryAcquire方法需要AQS的子類(lèi)自己去實(shí)現(xiàn),我們來(lái)看下ReentrantLock中的實(shí)現(xiàn):

可以看到,在ReentrantLock中,同步狀態(tài)State的含義如下:

State 資源的定義
0 表示鎖可用
1 表示鎖被占用
大于1 表示鎖被占用,且值表示同一線程的重入次數(shù)

ThreadA是首個(gè)獲取鎖的線程,所以上述方法會(huì)返回true,第一階段結(jié)束。(ThreadA一直保持占有鎖的狀態(tài))
此時(shí),AQS中的等待隊(duì)列還是空:

2.2 ThreadB開(kāi)始獲取鎖

終于,ThreadB要登場(chǎng)了,一樣,ThreadB先去調(diào)用lock方法,最終調(diào)用AQS的acquire方法:

tryAcquire方法肯定是返回false(因?yàn)榇藭r(shí)ThreadA占有著鎖)。
接下來(lái)看下addWaiter方法,這個(gè)方法其實(shí)就是將當(dāng)前調(diào)用線程包裝成一個(gè)【獨(dú)占結(jié)點(diǎn)】,添加到等待隊(duì)列尾部。

這里關(guān)鍵是enq方法,因?yàn)椴l(fā)插入的情況存在,所以該方法設(shè)計(jì)成了自旋操作,保證結(jié)點(diǎn)能成功插入,具體步驟如下:
①當(dāng)隊(duì)列為空的時(shí)候,先創(chuàng)建一個(gè)dummy頭結(jié)點(diǎn);

②進(jìn)入下一次循環(huán),插入隊(duì)尾結(jié)點(diǎn)。

好了,ThreadB已經(jīng)被包裝成結(jié)點(diǎn)插入隊(duì)尾了,接下來(lái)會(huì)調(diào)用acquireQueued方法,這也是AQS中最重要的方法之一:

在AQS中,等待隊(duì)列中的線程都是阻塞的,當(dāng)某個(gè)線程被喚醒時(shí),只有該線程是首結(jié)點(diǎn)(線程)時(shí),才有權(quán)去嘗試獲取鎖。

上述方法中,將ThreadB包裝成結(jié)點(diǎn)插入隊(duì)尾后,先判斷ThreadB是否是首結(jié)點(diǎn)(注意不是頭結(jié)點(diǎn),頭結(jié)點(diǎn)是個(gè)dummy結(jié)點(diǎn)),發(fā)現(xiàn)確實(shí)是首結(jié)點(diǎn)(node.predecessor==head),于是調(diào)用tryAcquire嘗試獲取鎖,但是獲取失敗了(此時(shí)ThreadA占有著鎖),就要判斷是否需要阻塞當(dāng)前線程。

判斷是否需要阻塞線程:

注意,對(duì)于獨(dú)占功能,只使用了3種結(jié)點(diǎn)狀態(tài):

結(jié)點(diǎn)狀態(tài) 描述
CANCELLED 1 取消。表示后驅(qū)結(jié)點(diǎn)被中斷或超時(shí),需要移出隊(duì)列
SIGNAL -1 發(fā)信號(hào)。表示后驅(qū)結(jié)點(diǎn)被阻塞了(當(dāng)前結(jié)點(diǎn)在入隊(duì)后、阻塞前,應(yīng)確保將其prev結(jié)點(diǎn)類(lèi)型改為SIGNAL,以便prev結(jié)點(diǎn)取消或釋放時(shí)將當(dāng)前結(jié)點(diǎn)喚醒。)
CONDITION -2 Condition專(zhuān)用。表示當(dāng)前結(jié)點(diǎn)在Condition隊(duì)列中,因?yàn)榈却硞€(gè)條件而被阻塞了

對(duì)于在等待隊(duì)列中的線程,如果要阻塞它,需要確保將來(lái)有線程可以喚醒它,AQS中通過(guò)將前驅(qū)結(jié)點(diǎn)的狀態(tài)置為SIGNAL:-1來(lái)表示將來(lái)會(huì)喚醒當(dāng)前線程,當(dāng)前線程可以安心的阻塞。

看下圖或許比較好理解:
①插入完ThreadB后,隊(duì)列的初始狀態(tài)如下:

②雖然ThreadB是隊(duì)首結(jié)點(diǎn),但是它拿不到鎖(被ThreadA占有著),所以ThreadB會(huì)阻塞,但在阻塞前需要設(shè)置下前驅(qū)的狀態(tài),以便將來(lái)可以喚醒我:

至此,ThreadB的執(zhí)行也暫告一段落了(安心得在等待隊(duì)列中睡覺(jué))。

注意:補(bǔ)充一點(diǎn),如果ThreadB在阻塞過(guò)程中被中斷,其實(shí)是不會(huì)拋出異常的,只會(huì)在acquireQueued方法返回時(shí),告訴調(diào)用者在阻塞器件有沒(méi)被中斷過(guò),具體如果處理,要不要拋出異常,取決于調(diào)用者,這其實(shí)是一種延時(shí)中斷機(jī)制。
2.3 ThreadC開(kāi)始獲取鎖

終于輪到ThreadC出場(chǎng)了,ThreadC的調(diào)用過(guò)程和ThreadB完全一樣,同樣拿不到鎖,然后加入到等待隊(duì)列隊(duì)尾:

然后,ThreadC在阻塞前需要把前驅(qū)結(jié)點(diǎn)的狀態(tài)置為SIGNAL:-1,以確保將來(lái)可以被喚醒:

至此,ThreadC的執(zhí)行也暫告一段落了(安心得在等待隊(duì)列中睡覺(jué))。

2.4 ThreadA釋放鎖

ThreadA終于使用完了臨界資源,要釋放鎖了,來(lái)看下ReentrantLock的unlock方法:

unlock內(nèi)部調(diào)用了AQS的release方法,傳參1:

嘗試釋放鎖的操作tryRelease

釋放成功后,調(diào)用unparkSuccessor方法,喚醒隊(duì)列中的首結(jié)點(diǎn):

此時(shí),隊(duì)列狀態(tài)為:

2.5 ThreadB喚醒后繼續(xù)執(zhí)行

好了,隊(duì)首結(jié)點(diǎn)(ThreadB)被喚醒了。
ThreadB會(huì)繼續(xù)從以下位置開(kāi)始執(zhí)行,先返回一個(gè)中斷標(biāo)識(shí),用于表示ThreadB在阻塞期間有沒(méi)被中斷過(guò):

然后ThreadB又開(kāi)始了自旋操作,被喚醒的是隊(duì)首結(jié)點(diǎn),所以可以嘗試tryAcquire獲取鎖,此時(shí)獲取成功(ThreadA已經(jīng)釋放了鎖)。
獲取成功后會(huì)調(diào)用setHead方法,將頭結(jié)點(diǎn)置為當(dāng)前結(jié)點(diǎn),并清除線程信息:

最終的隊(duì)列狀態(tài)如下:

2.6 ThreadB釋放鎖

ThreadB也終于使用完了臨界資源,要釋放鎖了,過(guò)程和ThreadA釋放時(shí)一樣,釋放成功后,會(huì)調(diào)用unparkSuccessor方法,喚醒隊(duì)列中的首結(jié)點(diǎn):

隊(duì)首結(jié)點(diǎn)(ThreadC)被喚醒后,繼續(xù)從原來(lái)的阻塞處向下執(zhí)行,并嘗試獲取鎖,獲取成功,最終隊(duì)列狀態(tài)如下:

2.7 ThreadC釋放鎖

ThreadC也終于使用完了臨界資源,要釋放鎖了。釋放成功后,調(diào)用unparkSuccessor方法,喚醒隊(duì)列中的首結(jié)點(diǎn):
此時(shí)隊(duì)列中只剩下一個(gè)頭結(jié)點(diǎn)(dummy),所以這個(gè)方法其實(shí)什么都不做。最終隊(duì)列的狀態(tài)就是只有一個(gè)dummy頭結(jié)點(diǎn)。

至此,AQS的獨(dú)占功能已經(jīng)差不多分析完了,剩下還有幾個(gè)內(nèi)容沒(méi)分析:

鎖中斷功能

限時(shí)等待功能

Conditon等待功能

這些功能將在后續(xù)章節(jié)陸續(xù)分析。

三、ReentrantLock的非公平策略原理

ReenrantLock非公平策略的內(nèi)部實(shí)現(xiàn)和公平策略沒(méi)啥太大區(qū)別:
非公平策略和公平策略的最主要區(qū)別在于:

公平鎖獲取鎖時(shí),會(huì)判斷等待隊(duì)列中是否有線程排在當(dāng)前線程前面。只有沒(méi)有情況下,才去獲取鎖,這是公平的含義。

非公平鎖獲取鎖時(shí),會(huì)立即嘗試修改同步狀態(tài),失敗后再調(diào)用AQS的acquire方法。

acquire方法會(huì)轉(zhuǎn)調(diào)非公平鎖自身的tryAcquire方法,其實(shí)最終是調(diào)了nofairTryAcquire方法,而該方法相對(duì)于公平鎖,只是少了“隊(duì)列中是否有其它線程排在當(dāng)前線程前”這一判斷:

四、AQS對(duì)中斷的支持

還是以ReentrantLock為例,來(lái)看下AQS是如何實(shí)現(xiàn)鎖中斷和超時(shí)的。
我們知道ReentrantLock的lockInterruptibly方法是會(huì)響應(yīng)中斷的。(線程如果在阻塞過(guò)程中被中斷,會(huì)拋出InterruptedException異常)

該方法調(diào)用了AQS的acquireInterruptibly方法:

上述代碼會(huì)先去嘗試獲取鎖,如果失敗,則調(diào)用doAcquireInterruptibly方法,如下:

很眼熟有木有?看下和acquireQueued方法的對(duì)比,唯一的區(qū)別就是:
當(dāng)調(diào)用線程獲取鎖失敗,進(jìn)入阻塞后,如果中途被中斷,acquireQueued只是用一個(gè)標(biāo)識(shí)記錄線程被中斷過(guò),而doAcquireInterruptibly則是直接拋出異常。

五、AQS對(duì)限時(shí)等待的支持

Lock接口中有一個(gè)方法:tryLock,用于在指定的時(shí)間內(nèi)嘗試獲取鎖,獲取不到就返回。
ReentrantLock實(shí)現(xiàn)了該方法,可以看到,該方法內(nèi)部調(diào)用了AQS的tryAcquireNanos方法:

tryAcquireNanos方法是響應(yīng)中斷的,先嘗試獲取一次鎖,失敗則調(diào)用doAcquireNanos方法進(jìn)行超時(shí)等待:

關(guān)鍵是doAcquireNano方法,和acquireQuqued方法類(lèi)似,又是一個(gè)自旋操作,在超時(shí)前不斷嘗試獲取鎖,獲取不到則阻塞(加上了等待時(shí)間的判斷)。該方法內(nèi)部,調(diào)用了LockSupport.parkNanos來(lái)超時(shí)阻塞線程:

LockSupport.parkNanos內(nèi)部其實(shí)通過(guò)Unsafe這個(gè)類(lèi)來(lái)操作線程的阻塞,底層是一個(gè)native方法:

如果當(dāng)前線程在指定時(shí)間內(nèi)獲取不到鎖,除了返回false外,最終還會(huì)執(zhí)行cancelAcquire方法:

示例

為了便于理解還是以3個(gè)線程為例:

假設(shè)現(xiàn)在有3個(gè)線程:ThreadA、ThreadB、ThreadC,一個(gè)公平的獨(dú)占鎖,3個(gè)線程會(huì)依次嘗試去獲取鎖,不過(guò)此時(shí)加上了限時(shí)等待:ThreadB等待10s,ThreadA等待20s。
ReentrantLock lock=new ReentrantLock(true);

//ThreadA    tryLock

//ThreadB    tryLock, 10s

//ThreadC    tryLock, 20s

//ThreadA    release

//ThreadB    release

//ThreadC    release

1. ThreadA首先獲取到鎖,ThreadB和ThreadC依次嘗試去獲取鎖
ThreadB和ThreadC經(jīng)過(guò)兩輪自旋操作后,等待隊(duì)列的情況如下:

2. ThreadB先到超時(shí)時(shí)間
調(diào)用了cancelAcquire方法取消操作,隊(duì)列狀態(tài)變成:

3. ThreadC到達(dá)超時(shí)時(shí)間
調(diào)用了cancelAcquire方法取消操作,隊(duì)列狀態(tài)變成:

在退出cancelAcquire后,原來(lái)ThreadB和ThreadC對(duì)應(yīng)的結(jié)點(diǎn)會(huì)被JVM垃圾回收器回收。

六、總結(jié)

本章從ReentrantLock入手,分析AQS的獨(dú)占功能的內(nèi)部實(shí)現(xiàn)細(xì)節(jié)。下一章,從CountDownLatch入手,看下AQS的共享功能如何實(shí)現(xiàn)。

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/76528.html

相關(guān)文章

  • Java線程進(jìn)階(三)—— J.U.Clocks框架:ReentrantLock

    摘要:公平策略在多個(gè)線程爭(zhēng)用鎖的情況下,公平策略?xún)A向于將訪問(wèn)權(quán)授予等待時(shí)間最長(zhǎng)的線程。使用方式的典型調(diào)用方式如下二類(lèi)原理的源碼非常簡(jiǎn)單,它通過(guò)內(nèi)部類(lèi)實(shí)現(xiàn)了框架,接口的實(shí)現(xiàn)僅僅是對(duì)的的簡(jiǎn)單封裝,參見(jiàn)原理多線程進(jìn)階七鎖框架獨(dú)占功能剖析 showImg(https://segmentfault.com/img/remote/1460000016012582); 本文首發(fā)于一世流云的專(zhuān)欄:https...

    jasperyang 評(píng)論0 收藏0
  • Java線程進(jìn)階(九)—— J.U.Clocks框架AQS共享功能剖析(4)

    摘要:好了,繼續(xù)向下執(zhí)行,嘗試獲取鎖失敗后,會(huì)調(diào)用首先通過(guò)方法,將包裝成共享結(jié)點(diǎn),插入等待隊(duì)列,插入完成后隊(duì)列結(jié)構(gòu)如下然后會(huì)進(jìn)入自旋操作,先嘗試獲取一次鎖,顯然此時(shí)是獲取失敗的主線程還未調(diào)用,同步狀態(tài)還是。 showImg(https://segmentfault.com/img/remote/1460000016012541); 本文首發(fā)于一世流云的專(zhuān)欄:https://segmentfa...

    CompileYouth 評(píng)論0 收藏0
  • Java線程進(jìn)階(十)—— J.U.Clocks框架:基于AQS的讀寫(xiě)鎖(5)

    摘要:關(guān)于,最后有兩點(diǎn)規(guī)律需要注意當(dāng)?shù)牡却?duì)列隊(duì)首結(jié)點(diǎn)是共享結(jié)點(diǎn),說(shuō)明當(dāng)前寫(xiě)鎖被占用,當(dāng)寫(xiě)鎖釋放時(shí),會(huì)以傳播的方式喚醒頭結(jié)點(diǎn)之后緊鄰的各個(gè)共享結(jié)點(diǎn)。當(dāng)?shù)牡却?duì)列隊(duì)首結(jié)點(diǎn)是獨(dú)占結(jié)點(diǎn),說(shuō)明當(dāng)前讀鎖被使用,當(dāng)讀鎖釋放歸零后,會(huì)喚醒隊(duì)首的獨(dú)占結(jié)點(diǎn)。 showImg(https://segmentfault.com/img/remote/1460000016012293); 本文首發(fā)于一世流云的專(zhuān)欄:...

    dunizb 評(píng)論0 收藏0
  • Java線程進(jìn)階(八)—— J.U.Clocks框架AQS的Conditon等待(3)

    摘要:關(guān)于接口的介紹,可以參見(jiàn)多線程進(jìn)階二鎖框架接口。最終線程釋放了鎖,并進(jìn)入阻塞狀態(tài)。當(dāng)線程被通知喚醒時(shí),則是將條件隊(duì)列中的結(jié)點(diǎn)轉(zhuǎn)換成等待隊(duì)列中的結(jié)點(diǎn),之后的處理就和獨(dú)占功能完全一樣。 showImg(https://segmentfault.com/img/remote/1460000016012490); 本文首發(fā)于一世流云的專(zhuān)欄:https://segmentfault.com/bl...

    ityouknow 評(píng)論0 收藏0
  • Java線程進(jìn)階(六)—— J.U.Clocks框架AQS綜述(1)

    摘要:在時(shí),引入了包,該包中的大多數(shù)同步器都是基于來(lái)構(gòu)建的??蚣芴峁┝艘惶淄ㄓ玫臋C(jī)制來(lái)管理同步狀態(tài)阻塞喚醒線程管理等待隊(duì)列。指針用于在結(jié)點(diǎn)線程被取消時(shí),讓當(dāng)前結(jié)點(diǎn)的前驅(qū)直接指向當(dāng)前結(jié)點(diǎn)的后驅(qū)完成出隊(duì)動(dòng)作。 showImg(https://segmentfault.com/img/remote/1460000016012438); 本文首發(fā)于一世流云的專(zhuān)欄:https://segmentfau...

    cocopeak 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

閱讀需要支付1元查看
<