自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

從ReentrantLock的角度思考AQS

開發(fā) 前端
我們上一篇簡單介紹了AQS這個(gè)技術(shù)點(diǎn),這一篇我們從ReentrantLock這個(gè)鎖的角度來分析AQS,幫助大家理解。

[[439467]]

我們上一篇簡單介紹了AQS這個(gè)技術(shù)點(diǎn),這一篇我們從ReentrantLock這個(gè)鎖的角度來分析AQS,幫助大家理解

[[439468]]

首先,我們先看一下ReentrantLock的內(nèi)部的抽象類Sync,這個(gè)是繼承于AQS的,重寫了其中的一些方法,我們會(huì)在下面源碼中解析,繼續(xù)往下看,記住這個(gè)Sync

我們知道這個(gè)鎖可以實(shí)現(xiàn)公平鎖和非公平鎖,我們來看下是如何實(shí)現(xiàn)的

  1. /** 
  2.     * Sync object for non-fair locks 
  3.     */ 
  4.    static final class NonfairSync extends Sync { 
  5.        private static final long serialVersionUID = 7316153563782823691L; 
  6.  
  7.        /** 
  8.         * Performs lock.  Try immediate barge, backing up to normal 
  9.         * acquire on failure. 
  10.         */ 
  11.        final void lock() { 
  12.            if (compareAndSetState(0, 1)) 
  13.                setExclusiveOwnerThread(Thread.currentThread()); 
  14.            else 
  15.                acquire(1); 
  16.         } 
  17.  
  18.        protected final boolean tryAcquire(int acquires) { 
  19.            return nonfairTryAcquire(acquires); 
  20.         } 
  21.  
  22.  
  23.    /** 
  24.     * Sync object for fair locks 
  25.     */ 
  26.    static final class FairSync extends Sync { 
  27.        private static final long serialVersionUID = -3000897897090466540L; 
  28.  
  29.        final void lock() { 
  30.             acquire(1); 
  31.         }} 

上面的是非公平鎖,下面的是公平鎖,默認(rèn)的是非公平鎖,我們看下非公平鎖的實(shí)現(xiàn)是先通過CAS的方式去加鎖,加鎖成功之后就將當(dāng)前線程設(shè)置為活躍的持有鎖的線程

  1. /** 
  2.      * The current owner of exclusive mode synchronization. 
  3.    */ 
  4.    private transient Thread exclusiveOwnerThread; 

失敗的話會(huì)執(zhí)行acquire方法,OK,這里我們?cè)倏聪鹿芥iFairSync的lock方法的實(shí)現(xiàn),這個(gè)公平鎖沒有像上面非公平鎖那樣判斷,而是直接調(diào)用了acquire方法

這里大家應(yīng)該也懂了非公平鎖和公平鎖的真正區(qū)別了吧,就是非公平鎖的時(shí)候,線程來的時(shí)候會(huì)多一次直接嘗試加鎖,剩下的操作就是一樣了

OK,讓我們進(jìn)去acquire方法看

看一下tryAcquire方法

可以看出,這里只是AQS的簡單實(shí)現(xiàn),具體獲取鎖的實(shí)現(xiàn)方法是由各自的公平鎖和非公平鎖單獨(dú)實(shí)現(xiàn)的(以ReentrantLock為例)

如果該方法返回了True,則說明當(dāng)前線程獲取鎖成功,就不用往后執(zhí)行了;如果獲取失敗,就需要加入到等待隊(duì)列中。下面會(huì)詳細(xì)解釋線程是何時(shí)以及怎樣被加入進(jìn)等待隊(duì)列中的。

OK,知道了這個(gè)我們就得看看ReentrantLock是如何實(shí)現(xiàn)tryAcquire方法的

老規(guī)矩,先看一下非公平鎖中的具體實(shí)現(xiàn)

大家看代碼應(yīng)該也比較好理解,第一步先判斷state==0,這個(gè)0也就意味著這個(gè)共享資源處于空閑狀態(tài),于是這里就會(huì)先嘗試去搶一下鎖,假如此時(shí)等待隊(duì)列中有等待線程,則就是等待線程中的第二個(gè)節(jié)點(diǎn)和這個(gè)新加入的這個(gè)線程去搶這個(gè)鎖了

為什么是第二個(gè),因?yàn)榈谝粋€(gè)head節(jié)點(diǎn)存儲(chǔ)的永遠(yuǎn)是占用鎖的線程節(jié)點(diǎn)Node

接下來就是判斷當(dāng)前持有鎖的線程和當(dāng)前線程是否是同一個(gè),如果是同一個(gè),則將state+1,這里就是ReentrantLock支持重入性的關(guān)鍵,到時(shí)候解鎖的時(shí)候也是通過減去這個(gè)state計(jì)數(shù)的

搶到鎖或者重入鎖,都會(huì)返回true,返回true,加鎖方法就直接加鎖了

如果既沒搶到鎖,又發(fā)現(xiàn)占用鎖的線程不是當(dāng)前線程,則返回false,繼續(xù)執(zhí)行

上面這是非公平鎖的tryAcquire方法,接下來咱再看這個(gè)公平鎖的tryAcquire方法

這個(gè)也是先判斷狀態(tài)是否為0,這個(gè)的==0之后的處理邏輯就很明了了,直接通過hasQueuedPredecessors方法判斷隊(duì)列中是否有等待的節(jié)點(diǎn),如果沒有等待的節(jié)點(diǎn),則直接通過CAS的方式進(jìn)行判斷,然后就是把當(dāng)前線程設(shè)置為活躍線程

如果有等待的節(jié)點(diǎn),就會(huì)跳過CAS的判斷,緊接著會(huì)去判斷當(dāng)前線程和持有鎖的線程是否是同一個(gè)線程,如果是同一個(gè)線程,還是進(jìn)行計(jì)數(shù)+1,滿足可重入性

不是就返回false,此時(shí)tryAcquire方法返回false

此時(shí),我們?cè)侔岩暯抢氐絘cquire方法

返回false之后,則會(huì)執(zhí)行addWaiter方法和acquireQueued方法

這段代碼首先會(huì)創(chuàng)建一個(gè)和當(dāng)前線程綁定的Node節(jié)點(diǎn),Node為雙向鏈表。此時(shí)等待對(duì)內(nèi)中的tail指針為空,直接調(diào)用enq(node)方法將當(dāng)前線程加入等待隊(duì)列尾部:

第一遍循環(huán)時(shí)tail指針為空,進(jìn)入if邏輯,使用CAS操作設(shè)置head指針,將head指向一個(gè)新創(chuàng)建的Node節(jié)點(diǎn)。

此時(shí)AQS中數(shù)據(jù):

執(zhí)行完成之后,head、tail、t都指向第一個(gè)Node元素。

接著執(zhí)行第二遍循環(huán),進(jìn)入else邏輯,此時(shí)已經(jīng)有了head節(jié)點(diǎn),這里要操作的就是將線程二對(duì)應(yīng)的Node節(jié)點(diǎn)掛到head節(jié)點(diǎn)后面。此時(shí)隊(duì)列中就有了兩個(gè)Node節(jié)點(diǎn):

addWaiter()方法執(zhí)行完后,會(huì)返回當(dāng)前線程創(chuàng)建的節(jié)點(diǎn)信息。繼續(xù)往后執(zhí)行acquireQueued(addWaiter(Node.EXCLUSIVE), arg)邏輯,此時(shí)傳入的參數(shù)為線程二對(duì)應(yīng)的Node節(jié)點(diǎn)信息

acquireQueued()這個(gè)方法會(huì)先判斷當(dāng)前傳入的Node對(duì)應(yīng)的前置節(jié)點(diǎn)是否為head,如果是則嘗試加鎖。

加鎖成功過則將當(dāng)前節(jié)點(diǎn)設(shè)置為head節(jié)點(diǎn),然后空置之前的head節(jié)點(diǎn),方便后續(xù)被垃圾回收掉。

如果加鎖失敗或者Node的前置節(jié)點(diǎn)不是head節(jié)點(diǎn),就會(huì)通過shouldParkAfterFailedAcquire方法 將head節(jié)點(diǎn)的waitStatus變?yōu)榱薙IGNAL=-1,最后執(zhí)行parkAndChecknIterrupt方法,調(diào)用LockSupport.park()掛起當(dāng)前線程。

我們不能發(fā)現(xiàn)的一點(diǎn),就是AQS的設(shè)計(jì)內(nèi)部,包括ReentrantLock的設(shè)計(jì)內(nèi)部。很多地方都會(huì)嘗試用CAS的方式去加鎖,就是因?yàn)樵诟咚俚倪\(yùn)轉(zhuǎn)下,可能在幾行代碼的時(shí)間一個(gè)線程就已經(jīng)用完鎖了,這樣可以最高效率的來利用資源

parkAndCheckInterrupt主要用于掛起當(dāng)前線程,阻塞調(diào)用棧,返回當(dāng)前線程的中斷狀態(tài)。

給大家看個(gè)這里的流程圖,圖片來源于網(wǎng)絡(luò),覺得挺不錯(cuò)

從上圖可以看出,跳出當(dāng)前循環(huán)的條件是當(dāng)“前置節(jié)點(diǎn)是頭結(jié)點(diǎn),且當(dāng)前線程獲取鎖成功”。

為了防止因死循環(huán)導(dǎo)致CPU資源被浪費(fèi),我們會(huì)判斷前置節(jié)點(diǎn)的狀態(tài)來決定是否要將當(dāng)前線程掛起,具體掛起流程用流程圖表示如下(shouldParkAfterFailedAcquire流程):

acquireQueued中最后的finally中,如果失敗,則執(zhí)行cancelAcquire

獲取當(dāng)前節(jié)點(diǎn)的前驅(qū)節(jié)點(diǎn),如果前驅(qū)節(jié)點(diǎn)的狀態(tài)是CANCELLED,那就一直往前遍歷,找到第一個(gè)waitStatus <= 0的節(jié)點(diǎn),將找到的Pred節(jié)點(diǎn)和當(dāng)前Node關(guān)聯(lián),將當(dāng)前Node設(shè)置為CANCELLED。

但是為什么所有的變化都是對(duì)Next指針進(jìn)行了操作,而沒有對(duì)Prev指針進(jìn)行操作呢?什么情況下會(huì)對(duì)Prev指針進(jìn)行操作?

執(zhí)行cancelAcquire的時(shí)候,當(dāng)前節(jié)點(diǎn)的前置節(jié)點(diǎn)可能已經(jīng)從隊(duì)列中出去了(已經(jīng)執(zhí)行過Try代碼塊中的shouldParkAfterFailedAcquire方法了),如果此時(shí)修改Prev指針,有可能會(huì)導(dǎo)致Prev指向另一個(gè)已經(jīng)移除隊(duì)列的Node,因此這塊變化Prev指針不安全。

shouldParkAfterFailedAcquire方法中,會(huì)執(zhí)行下面的代碼,其實(shí)就是在處理Prev指針。shouldParkAfterFailedAcquire是獲取鎖失敗的情況下才會(huì)執(zhí)行,進(jìn)入該方法后,說明共享資源已被獲取,當(dāng)前節(jié)點(diǎn)之前的節(jié)點(diǎn)都不會(huì)出現(xiàn)變化,因此這個(gè)時(shí)候變更Prev指針比較安全。

  1. do { 
  2.   node.prev = pred = pred.prev; 
  3.  } while (pred.waitStatus > 0); 

解鎖

接下來再對(duì)解鎖的基本流程進(jìn)行分析。由于ReentrantLock在解鎖的時(shí)候,并不區(qū)分公平鎖和非公平鎖,所以我們直接看解鎖的源碼:

點(diǎn)進(jìn)來release之后發(fā)現(xiàn)實(shí)現(xiàn)還是在AQS框架中

在ReentrantLock里面的公平鎖和非公平鎖的父類Sync定義了可重入鎖的釋放鎖機(jī)制。

這個(gè)方法先去減少一次可重入次數(shù),然后判斷當(dāng)前線程是否是持有鎖的線程,如果不是,則直接拋出異常

接著判斷c==0,等于0代表當(dāng)前的資源處于空閑狀態(tài),便可以將當(dāng)前獨(dú)占資源的線程設(shè)置為null,然后更新state

如果不等于0,這一步釋放獨(dú)占鎖的操作便會(huì)濾過,就是普通的重入鎖減少一次重入次數(shù),就像是重入加鎖三次,執(zhí)行這里之后只是變成2次而已,但是還是該線程持有該資源

總結(jié)

我們先是在非公平鎖和公平鎖的角度分別分析了加鎖的過程,得知非公平比公平鎖只是多了一個(gè)搶先加鎖的機(jī)會(huì),但是如果搶不到鎖還是會(huì)執(zhí)行和公平鎖相同的邏輯

中間我們分析了公平鎖和非公平鎖的優(yōu)缺點(diǎn),這個(gè)是面試熱點(diǎn)

然后我們還會(huì)發(fā)現(xiàn)代碼中很多地方都會(huì)嘗試用CAS的方式去搶占鎖,我們知道CPU的運(yùn)行是很快的,這樣能夠保證資源釋放釋放能夠在第一時(shí)間被等待隊(duì)列中的線程搶到鎖

最后我們又分析了這個(gè)釋放鎖的過程,這個(gè)釋放鎖并沒有公平和非公平的區(qū)分,只是其中對(duì)于重入鎖進(jìn)行了處理,就是上面最后一張圖的==0操作,因?yàn)槲覀兩厦娣治隽酥厝氲牡览硪彩菍?duì)這個(gè)state進(jìn)行累加得來的,所以這里只需要減一,然后判斷是否為0即可

0的時(shí)候就意味著此時(shí)資源處于空閑狀態(tài),這個(gè)state是volatile的,保證了可見性

這篇只是一個(gè)籠統(tǒng)的分析,其實(shí)還有很多細(xì)節(jié)沒有分析到位,只能說AQS的設(shè)計(jì)很精妙,李老牛皮

 

責(zé)任編輯:姜華 來源: Java賊船
相關(guān)推薦

2023-04-14 08:39:01

AQS方法JDK5

2010-04-17 13:17:29

網(wǎng)絡(luò)安全管理策略web安全

2022-07-11 20:46:39

AQSJava

2020-11-16 08:11:32

ReentrantLo

2019-04-28 16:10:50

設(shè)計(jì)Redux前端

2010-07-09 10:13:42

UDP協(xié)議

2022-03-08 11:29:06

Linux進(jìn)程系統(tǒng)

2015-10-12 10:07:36

數(shù)據(jù)藝術(shù)市場(chǎng)

2015-05-05 11:04:31

CoreOS自動(dòng)化運(yùn)維

2021-02-06 23:21:35

SaaS開發(fā)低代碼

2011-12-27 11:14:36

Java

2011-06-16 17:49:00

SEO

2009-02-13 11:25:58

華為移動(dòng)無線商業(yè)周刊

2012-04-29 10:37:28

APP

2010-07-16 09:00:20

開源RedOffice紅旗2000

2013-12-11 21:48:38

OpenStack

2024-01-12 07:38:38

AQS原理JUC

2020-12-17 08:03:57

LinkedList面試源碼

2020-12-14 08:03:52

ArrayList面試源碼

2020-02-04 09:53:05

數(shù)據(jù)安全數(shù)據(jù)泄漏信息安全
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)