一区二区三区在线-一区二区三区亚洲视频-一区二区三区亚洲-一区二区三区午夜-一区二区三区四区在线视频-一区二区三区四区在线免费观看

服務器之家:專注于服務器技術及軟件下載分享
分類導航

PHP教程|ASP.NET教程|Java教程|ASP教程|編程技術|正則表達式|C/C++|IOS|C#|Swift|Android|VB|R語言|JavaScript|易語言|vb.net|

服務器之家 - 編程語言 - Java教程 - 從ReentrantLock的角度思考AQS

從ReentrantLock的角度思考AQS

2021-12-09 22:13Java賊船Captain Java教程

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

從ReentrantLock的角度思考AQS

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

從ReentrantLock的角度思考AQS

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

從ReentrantLock的角度思考AQS

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

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

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

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

失敗的話會執行acquire方法,OK,這里我們再看下公平鎖FairSync的lock方法的實現,這個公平鎖沒有像上面非公平鎖那樣判斷,而是直接調用了acquire方法

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

OK,讓我們進去acquire方法看

從ReentrantLock的角度思考AQS

看一下tryAcquire方法

從ReentrantLock的角度思考AQS

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

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

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

老規矩,先看一下非公平鎖中的具體實現

從ReentrantLock的角度思考AQS

從ReentrantLock的角度思考AQS

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

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

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

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

從ReentrantLock的角度思考AQS

如果既沒搶到鎖,又發現占用鎖的線程不是當前線程,則返回false,繼續執行

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

從ReentrantLock的角度思考AQS

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

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

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

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

從ReentrantLock的角度思考AQS

返回false之后,則會執行addWaiter方法和acquireQueued方法

從ReentrantLock的角度思考AQS

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

從ReentrantLock的角度思考AQS

第一遍循環時tail指針為空,進入if邏輯,使用CAS操作設置head指針,將head指向一個新創建的Node節點。

此時AQS中數據:

從ReentrantLock的角度思考AQS

執行完成之后,head、tail、t都指向第一個Node元素。

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

從ReentrantLock的角度思考AQS

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

從ReentrantLock的角度思考AQS

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

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

如果加鎖失敗或者Node的前置節點不是head節點,就會通過shouldParkAfterFailedAcquire方法 將head節點的waitStatus變為了SIGNAL=-1,最后執行parkAndChecknIterrupt方法,調用LockSupport.park()掛起當前線程。

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

parkAndCheckInterrupt主要用于掛起當前線程,阻塞調用棧,返回當前線程的中斷狀態。

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

從ReentrantLock的角度思考AQS

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

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

從ReentrantLock的角度思考AQS

acquireQueued中最后的finally中,如果失敗,則執行cancelAcquire

獲取當前節點的前驅節點,如果前驅節點的狀態是CANCELLED,那就一直往前遍歷,找到第一個waitStatus <= 0的節點,將找到的Pred節點和當前Node關聯,將當前Node設置為CANCELLED。

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

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

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

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

解鎖

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

從ReentrantLock的角度思考AQS

點進來release之后發現實現還是在AQS框架中

從ReentrantLock的角度思考AQS

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

從ReentrantLock的角度思考AQS

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

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

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

總結

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

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

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

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

0的時候就意味著此時資源處于空閑狀態,這個state是volatile的,保證了可見性

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

原文鏈接:https://mp.weixin.qq.com/s/O3zw_Pjdrzdkh4xlNlJlew

延伸 · 閱讀

精彩推薦
  • Java教程升級IDEA后Lombok不能使用的解決方法

    升級IDEA后Lombok不能使用的解決方法

    最近看到提示IDEA提示升級,尋思已經有好久沒有升過級了。升級完畢重啟之后,突然發現好多錯誤,本文就來介紹一下如何解決,感興趣的可以了解一下...

    程序猿DD9332021-10-08
  • Java教程Java實現搶紅包功能

    Java實現搶紅包功能

    這篇文章主要為大家詳細介紹了Java實現搶紅包功能,采用多線程模擬多人同時搶紅包,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙...

    littleschemer13532021-05-16
  • Java教程20個非常實用的Java程序代碼片段

    20個非常實用的Java程序代碼片段

    這篇文章主要為大家分享了20個非常實用的Java程序片段,對java開發項目有所幫助,感興趣的小伙伴們可以參考一下 ...

    lijiao5352020-04-06
  • Java教程Java使用SAX解析xml的示例

    Java使用SAX解析xml的示例

    這篇文章主要介紹了Java使用SAX解析xml的示例,幫助大家更好的理解和學習使用Java,感興趣的朋友可以了解下...

    大行者10067412021-08-30
  • Java教程Java8中Stream使用的一個注意事項

    Java8中Stream使用的一個注意事項

    最近在工作中發現了對于集合操作轉換的神器,java8新特性 stream,但在使用中遇到了一個非常重要的注意點,所以這篇文章主要給大家介紹了關于Java8中S...

    阿杜7482021-02-04
  • Java教程Java BufferWriter寫文件寫不進去或缺失數據的解決

    Java BufferWriter寫文件寫不進去或缺失數據的解決

    這篇文章主要介紹了Java BufferWriter寫文件寫不進去或缺失數據的解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望...

    spcoder14552021-10-18
  • Java教程xml與Java對象的轉換詳解

    xml與Java對象的轉換詳解

    這篇文章主要介紹了xml與Java對象的轉換詳解的相關資料,需要的朋友可以參考下...

    Java教程網2942020-09-17
  • Java教程小米推送Java代碼

    小米推送Java代碼

    今天小編就為大家分享一篇關于小米推送Java代碼,小編覺得內容挺不錯的,現在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧...

    富貴穩中求8032021-07-12
主站蜘蛛池模板: 久久精品国产免费 | 欧美男同互吃gay老头 | 欧美成年黄网站色高清视频 | 99久久免费精品视频 | 日韩一二三 | 亚洲精品卡一卡2卡3卡4卡 | 日本伊人久久 | 亚洲国产果果在线播放在线 | 亚洲视频男人的天堂 | 国产高清不卡视频在线播放 | 国产成人h综合亚洲欧美在线 | 国产图片综合区 | 色综合精品 | 福利入口在线观看 | 韩国悲惨事件30无删减在线 | 荷兰精品女人性hd | 99久久99热久久精品免 | 变态 调教 视频 国产九色 | 91久久碰国产 | 欧美午夜性春猛交bbb | 精品国产无限资源免费观看 | 9久热这里只有精品视频在线观看 | 网www天堂资源在线 王淑兰与铁柱全文免费阅读 | 午夜福到在线4国产 | 亚洲精品色综合久久 | 青草热久精品视频在线观看 | 精品一区二区91 | 6969精品视频在线观看 | 日本黄色网页 | 91免费高清视频 | 色老板视频在线 | 百合文高h | 日产中文乱码卡一卡二 | 欧美丝袜videohd | 国内小情侣一二三区在线视频 | 免费岛国| 蜜桃视频在线观看www | 午夜 在线播放 | 色综合视频一区二区三区 | 亚洲va欧美va国产va天堂影 | 春意影院午夜爽爽爽免费 |