當前位置:
首頁 > 知識 > 深入學習Java同步器AQS

深入學習Java同步器AQS

介紹:

AQS(AbstractQueuedSynchronizer類)是一個用來構建鎖和同步器的框架,它在內部定義了一個int state變數,用來表示同步狀態.在LOCK包中的相關鎖(常用的有ReentrantLock、 ReadWriteLock)都是基於AQS來構建.然而這些鎖都沒有直接來繼承AQS,而是定義了一個Sync類去繼承AQS.那麼為什麼要這樣呢?because:鎖面向的是使用用戶,而同步器面向的則是線程式控制制,那麼在鎖的實現中聚合同步器而不是直接繼承AQS就可以很好的隔離二者所關注的事情.

AQS是通過一個雙向的FIFO 同步隊列來完成同步狀態的管理,當有線程獲取鎖失敗後,就被添加到隊列末尾,讓我看一下這個隊列

深入學習Java同步器AQS

紅色節點為頭結點,可以把它當做正在持有鎖的節點,

深入學習Java同步器AQS

由上可知,它把head和tail設置為了volatile,這兩個節點的修改將會被其他線程看到,事實上,我們也主要是通過修改這兩個節點來完成入隊和出隊.接下來一起我們一起學習Node

深入學習Java同步器AQS

下面解釋下waitStatus五個的得含義:

  • CANCELLED(1):該節點的線程可能由於超時或被中斷而處於被取消(作廢)狀態,一旦處於這個狀態,節點狀態將一直處於CANCELLED(作廢),因此應該從隊列中移除.

  • SIGNAL(-1):當前節點為SIGNAL時,後繼節點會被掛起,因此在當前節點釋放鎖或被取消之後必須被喚醒(unparking)其後繼結點.

  • CONDITION(-2) 該節點的線程處於等待條件狀態,不會被當作是同步隊列上的節點,直到被喚醒(signal),設置其值為0,重新進入阻塞狀態.

  • 0:新加入的節點

在鎖的獲取時,並不一定只有一個線程才能持有這個鎖(或者稱為同步狀態),所以此時有了獨佔模式和共享模式的區別,也就是在Node節點中由nextWait來標識。比如ReentrantLock就是一個獨佔鎖,只能有一個線程獲得鎖,而WriteAndReadLock的讀鎖則能由多個線程同時獲取,但它的寫鎖則只能由一個線程持有。這次先介紹獨佔模式下鎖(或者稱為同步狀態)的獲取與釋放.這個類使用到了模板方法設計模式:定義一個操作中演算法的骨架,而將一些步驟的實現延遲到子類中。

1. 獨佔模式同步狀態的獲取

深入學習Java同步器AQS

該方法首先嘗試獲取鎖( tryAcquire(arg)的具體實現定義在了子類中),如果獲取到,則執行完畢,否則通過addWaiter(Node.EXCLUSIVE), arg)方法把當前節點添加到等待隊列末尾,並設置為獨佔模式,

深入學習Java同步器AQS

深入學習Java同步器AQS

如果tail節點為空,執行enq(node);重新嘗試,最終把node插入.在把node插入隊列末尾後,它並不立即掛起該節點中線程,因為在插入它的過程中,前面的線程可能已經執行完成,所以它會先進行自旋操作acquireQueued(node, arg),嘗試讓該線程重新獲取鎖!當條件滿足獲取到了鎖則可以從自旋過程中退出,否則繼續。

深入學習Java同步器AQS

如果沒獲取到鎖,則判斷是否應該掛起,而這個判斷則得通過它的前驅節點的waitStatus來確定:

深入學習Java同步器AQS

如果前驅節點的waitStatus為:

  • SIGNAL,則返回true表示應該掛起當前線程,掛起該線程,並等待被喚醒,被喚醒後進行中斷檢測,如果發現當前線程被中斷,那麼拋出InterruptedException並退出循環.

  • >0,將前驅節點踢出隊列,返回false

  • <0,也是返回false,不過先將前驅節點waitStatus設置為SIGNAL,使得下次判斷時,將當前節點掛起.

最後,我們對獲取獨佔式鎖過程對做個總結:

AQS的模板方法acquire通過調用子類自定義實現的tryAcquire獲取同步狀態失敗後->將線程構造成Node節點(addWaiter)->將Node節點添加到同步隊列對尾(addWaiter)->節點以自旋的方法獲取同步狀態(acquirQueued)。在節點自旋獲取同步狀態時,只有其前驅節點是頭節點的時候才會嘗試獲取同步狀態,如果該節點的前驅不是頭節點或者該節點的前驅節點是頭節點單獲取同步狀態失敗,則判斷當前線程需要阻塞,如果需要阻塞則需要被喚醒過後才返回。

2. 獨佔模式同步狀態的釋放

既然是釋放,那肯定是持有鎖的該線程執行釋放操作,即head節點中的線程釋放鎖.

AQS中的release釋放同步狀態和acquire獲取同步狀態一樣,都是模板方法,tryRelease釋放的具體操作都有子類去實現,父類AQS只提供一個演算法骨架。

深入學習Java同步器AQS

過程:首先調用子類的tryRelease()方法釋放鎖,然後喚醒後繼節點,在喚醒的過程中,需要判斷後繼節點是否滿足情況,如果後繼節點不為且不是作廢狀態,則喚醒這個後繼節點,否則從tail節點向前尋找合適的節點,如果找到,則喚醒.

綜上,我們已經描述完了獨佔鎖的獲取和釋放,至於共享鎖的操作,有時間會再聊,請持續關注!


學習Java的同學注意了!!!

學習過程中遇到什麼問題或者想獲取學習資源的話,歡迎加入Java學習交流群495273252,我們一起學Java!

喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 Java團長 的精彩文章:

雜談:關於程序員職業發展的兩三事
談架構師的基本素養和乾貨日誌處理
IT培訓行業揭秘
Java編程之反射中的註解詳解

TAG:Java團長 |

您可能感興趣

深入剖析機器學習框架平台Pegasus
入門 | 通過 Q-learning 深入理解強化學習
深入學習Redis:Redis內存模型
深入理解 ES Modules
通過 Q-learning 深入理解強化學習
蘋果A12深入測試:CPU性能媲美Intel Skylake
Python模塊深入學習
深入對比數據科學工具箱:SparkR vs Sparklyr
深入分析利用宏代碼傳播NetwiredRC和Quasar RAT的惡意RTF文檔
深入了解Valve Index的FOV視場設計
深入近賞劃時代手機 Samsung Galaxy Fold
藉助DeepSqueak深度學習演算法科學家對鼠語展開深入研究
蘋果A12仿生深入測試:媲美Intel Skylake桌面CPU
深入解讀Google Lens
深入 git rebase
MapReduce Shuffle深入理解
深入了解美國潮流品牌SOME WARE的主理人Brendan Fowler
關於Sharpay ICO的深入分析
convergencias展覽 深入探索古巴設計
深入 Spring Boot :實現對 Fat Jar jsp 的支持