<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          從ReentrantLock的角度思考AQS

          共 4267字,需瀏覽 9分鐘

           ·

          2021-12-13 07:38


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


          ? ? ? ?

          ?

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



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

          /**????*?Sync?object?for?non-fair?locks????*/???static?final?class?NonfairSync?extends?Sync?{???????private?static?final?long?serialVersionUID?=?7316153563782823691L;
          ???????/**????????*?Performs?lock.??Try?immediate?barge,?backing?up?to?normal????????*?acquire?on?failure.????????*/???????final?void?lock()?{???????????if?(compareAndSetState(0,?1))???????????????setExclusiveOwnerThread(Thread.currentThread());???????????else???????????????acquire(1); }
          ???????protected?final?boolean?tryAcquire(int?acquires)?{???????????return?nonfairTryAcquire(acquires); }}

          ???/**????*?Sync?object?for?fair?locks????*/???static?final?class?FairSync?extends?Sync?{???????private?static?final?long?serialVersionUID?=?-3000897897090466540L;
          ???????final?void?lock()?{ acquire(1);????????}}


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


          /**     * The current owner of exclusive mode synchronization.???*/???private?transient?Thread?exclusiveOwnerThread;


          失敗的話會執(zhí)行acquire方法,OK,這里我們再看下公平鎖FairSynclock方法的實現(xiàn),這個公平鎖沒有像上面非公平鎖那樣判斷,而是直接調(diào)用了acquire方法

          ?

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

          ?

          OK,讓我們進去acquire方法看



          看一下tryAcquire方法



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


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

          ?

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

          ?

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




          ?

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

          為什么是第二個,因為第一個head節(jié)點存儲的永遠是占用鎖的線程節(jié)點Node

          ?

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

          ?

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



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

          ?

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



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

          ?

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

          ?

          不是就返回false,此時tryAcquire方法返回false

          ?

          此時,我們再把視角拉回到acquire方法



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



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



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


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



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

          ?

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



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




          acquireQueued()這個方法會先判斷當前傳入的Node對應的前置節(jié)點是否為head,如果是則嘗試加鎖。


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

          ?

          如果加鎖失敗或者Node的前置節(jié)點不是head節(jié)點,就會通過shouldParkAfterFailedAcquire方法 將head節(jié)點的waitStatus變?yōu)榱?span style="font-family: Calibri;">SIGNAL=-1,最后執(zhí)行parkAndChecknIterrupt方法,調(diào)用LockSupport.park()掛起當前線程。


          ? ? ? ? ? ?

          ?

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

          ?

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

          ?

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

          ?


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


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



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

          ?

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


          ? ? ? ? ??

          ?

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

          ?

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


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

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


          解鎖

          ?

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



          點進來release之后發(fā)現(xiàn)實現(xiàn)還是在AQS框架中



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



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

          ?

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

          ?

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

          ?

          總結(jié)?

          ?

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

          ?

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

          ?

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

          ?

          最后我們又分析了這個釋放鎖的過程,這個釋放鎖并沒有公平和非公平的區(qū)分,只是其中對于重入鎖進行了處理,就是上面最后一張圖的==0操作,因為我們上面分析了重入的道理也是對這個state進行累加得來的,所以這里只需要減一,然后判斷是否為0即可

          ?

          0的時候就意味著此時資源處于空閑狀態(tài),這個statevolatile的,保證了可見性

          ?

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



          結(jié)束語


          感謝大家能夠做我最初的讀者和傳播者,請大家相信,只要你給我一份愛,我終究會還你們一頁情的。


          Captain會持續(xù)更新技術(shù)文章,和生活中的暴躁文章,歡迎大家關(guān)注【Java賊船】,成為船長的學習小伙伴,和船長一起乘千里風、破萬里浪


          哦對了,后續(xù)所有的文章都會更新到這里


          https://github.com/DayuMM2021/Java



          瀏覽 92
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  欧美午夜激情视频 | 干少妇AV | AV无码在线免费观看 | 一本色道综合久久欧美日韩精品 | 日本高清AⅤ在线播放 |