成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

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

JayChen / 3548人閱讀

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

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

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

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

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

分析AbstractQueuedSynchronizer的鎖中斷、限時等待等功能

二、ReentrantLock的公平策略原理

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

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

線程的操作時序如下:

//ThreadA    lock

//ThreadB    lock

//ThreadC    lock

//ThreadA    release

//ThreadB    release

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

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

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

acquire方法來自AQS:

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

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

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

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

2.2 ThreadB開始獲取鎖

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

tryAcquire方法肯定是返回false(因為此時ThreadA占有著鎖)。
接下來看下addWaiter方法,這個方法其實就是將當(dāng)前調(diào)用線程包裝成一個【獨占結(jié)點】,添加到等待隊列尾部。

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

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

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

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

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

判斷是否需要阻塞線程:

注意,對于獨占功能,只使用了3種結(jié)點狀態(tài):

結(jié)點狀態(tài) 描述
CANCELLED 1 取消。表示后驅(qū)結(jié)點被中斷或超時,需要移出隊列
SIGNAL -1 發(fā)信號。表示后驅(qū)結(jié)點被阻塞了(當(dāng)前結(jié)點在入隊后、阻塞前,應(yīng)確保將其prev結(jié)點類型改為SIGNAL,以便prev結(jié)點取消或釋放時將當(dāng)前結(jié)點喚醒。)
CONDITION -2 Condition專用。表示當(dāng)前結(jié)點在Condition隊列中,因為等待某個條件而被阻塞了

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

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

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

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

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

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

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

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

2.4 ThreadA釋放鎖

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

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

嘗試釋放鎖的操作tryRelease

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

此時,隊列狀態(tài)為:

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

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

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

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

2.6 ThreadB釋放鎖

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

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

2.7 ThreadC釋放鎖

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

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

鎖中斷功能

限時等待功能

Conditon等待功能

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

三、ReentrantLock的非公平策略原理

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

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

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

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

四、AQS對中斷的支持

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

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

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

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

五、AQS對限時等待的支持

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

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

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

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

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

示例

為了便于理解還是以3個線程為例:

假設(shè)現(xiàn)在有3個線程:ThreadA、ThreadB、ThreadC,一個公平的獨占鎖,3個線程會依次嘗試去獲取鎖,不過此時加上了限時等待: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)過兩輪自旋操作后,等待隊列的情況如下:

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

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

在退出cancelAcquire后,原來ThreadB和ThreadC對應(yīng)的結(jié)點會被JVM垃圾回收器回收。

六、總結(jié)

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

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

轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/76528.html

相關(guān)文章

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

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

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

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

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

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

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

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

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

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

    cocopeak 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<