歷史
1995年sun公司釋出了第一個java語言版本,可以說從jdk1.1到jdk1.4期間java的使用主要是在移動應用和中小型企業應用中,在此類領域中基本不用設計大型併發場景,當然也沒有大型網際網路公司使用java,因為擔心它本身的效能。
在網際網路及伺服器硬體迅猛的發展下,sun公司更加註重企業級應用方面,毫無疑問高併發是一個重要的主題,於是在J2SE5.0(jdk1.5)代號為老虎的版本中增加了更加強大的併發相關的操作包——java.util.concurrent。
此後java在高併發中表現優異,很多大型網際網路公司都使用java作為主要開發語言,例如阿里巴巴、ebay等,這些公司系統的訪問絕對是屬於世界級的大型併發場景,反映了java在大型併發場景是可行的。
AQS框架
Jdk的併發包提供了各種鎖及同步機制,其實現的核心類是AbstractQueuedSynchronizer,我們簡稱為AQS框架,它為不同場景提供了實現鎖及同步機制的基本框架,為同步狀態的原子性管理、執行緒的阻塞、執行緒的解除阻塞及排隊管理提供了一種通用的機制。
Jdk的併發包(juc)的作者是Doug Lea,但其中思想卻是結合了多位大師的智慧,如果你想深入理解juc的相關理論可以參考Doug Lea寫的《The_java.util.concurrent_Synchronizer_Framework》論文。從這裡可以找到AQS的理論基礎,包括框架的基本原理、需求、設計、實現思路、用法及效能,由於這些方面篇幅較大,本文不打算涉及所有方面,主要將針對AQS類的結構及相關操作進行分析。
AQS佇列
AQS將執行緒封裝到一個Node裡面,並維護一個CHL Node FIFO佇列,它是一個非阻塞的FIFO佇列,也就是說在併發條件下往此佇列做插入或移除操作不會阻塞,是通過自旋鎖和CAS保證節點插入和移除的原子性,實現無鎖快速插入。
其實AbstractQueuedSynchronizer主要就是維護了一個state屬性、一個FIFO佇列和執行緒的阻塞與解除阻塞操作。state表示同步狀態,它的型別為32位整型,對state的更新必須要保證原子性。這裡的佇列是一個雙向連結串列,每個節點裡面都有一個prev和next,它們分別是前一個節點和後一個節點的引用。需要注意的是此雙向連結串列除了鏈頭其他每個節點內部都包含一個執行緒,而鏈頭可以理解為一個空節點。
佇列結構
對於佇列的結構我們需要深入理解下,下圖展示的是組成雙向連結串列其中一個節點的結構,該節點包含五個主要元素,表示的意思如下表,
- Node prev:前驅節點,指向前一個節點
- Node next:後續節點,指向後一個節點
- Node nextWaiter:用於儲存condition佇列的後續節點
- Thread thread:入佇列時的當前執行緒
- int waitStatus:有五種狀態:
- SIGNAL,值為-1,表示當前節點的後續節點中的執行緒通過park被阻塞了,當前節點在釋放或取消時要通過unpark解除它的阻塞。
- CANCELLED,值為1,表示當前節點的執行緒因為超時或中斷被取消了。
- CONDITION,值為-2,表示當前節點在condition佇列中。
- PROPAGATE,值為-3,共享模式的頭結點可能處於此狀態,表示無條件往下傳播,引入此狀態是為了優化鎖競爭,使佇列中執行緒有序地一個一個喚醒。
- 0,除了以上四種狀態的第五種狀態,一般是節點初始狀態。
前驅節點prev的引入主要是為了完成超時及取消語義,前驅節點取消後只需向前找到一個未取消的前驅節點即可;後續節點的引入主要是為了優化後續節點的查詢,避免每次從尾部向前查詢;nextWaiter用於表示condition佇列的後續節點,此時prev和next屬性將不再使用,而且節點狀態處於Node.CONDITION; waitStatus表示的是後續節點狀態,這是因為AQS中使用CLH佇列實現執行緒的結構管理,而CLH結構正是用前一節點某一屬性表示當前節點的狀態,這樣更容易實現取消和超時功能。
總結
上面是對節點及節點組成佇列的結構的介紹,後面戶口介紹AQS相關的一些操作,包括鎖的獲取與釋放、佇列的管理、同步狀態的更新、執行緒阻塞與喚醒、取消中斷與超時中斷等等。
-------------推薦閱讀------------
我的開源專案彙總(機器&深度學習、NLP、網路IO、AIML、mysql協議、chatbot)
跟我交流,向我提問:
歡迎關注: