你必須非常努力,才能看起來毫不費力! 微信搜索公眾號[ 漫漫Coding路 ],一起From Zero To Hero ! 前言 日常 Go 開發中,Context 包是用的最多的一個了,幾乎所有函數的第一個參數都是 ctx,那麼我們為什麼要傳遞 Context 呢,Context 又有哪些用法,底 ...
整體介紹
概念
首先閱讀一下類的源碼註釋,可以知道,這幾個介面是最關鍵的。
這幾個方法是使用AQS類的關鍵,只有這幾個方法是可以定製的,其他方法幾乎都是final的,不可修改。
從代碼實現上看,能看到的變數幾乎都是volatile的,能看到的方法幾乎都是CAS或者Unsafe類的原子方法。
接下來我們來整理一下這個類的欄位和方法,這裡我們主要關註private欄位和public方法。
欄位
volatile int state:同步狀態。
volatile Node head:等待隊列的頭,延遲初始化。
volatile Node tail:等待隊列的尾部。初始化後,僅通過casTail修改。
方法
獲取鎖:
acquire(int arg)
acquireShared(int arg)
釋放鎖:
release(int arg)
releaseShared(int arg)
等待隊列:
hasQueuedThreads()
getFirstQueuedThread()
isQueued(Thread thread)
getQueuedThreads()
getExclusiveQueuedThreads()
getSharedQueuedThreads()
條件對象ConditionObject:
await()
signal()
架構
AQS整體結構,包括加鎖/釋放鎖,條件對象await/signal。
功能說明
鎖
數據結構
鎖的數據結構
一個狀態欄位state表示同步狀態,0表示沒有鎖競爭,1表示有鎖競爭。
head跟tail分別是等待隊列的頭節點和尾節點,該等待隊列是用雙向鏈表實現的。
節點的數據結構
prev:前驅節點
next:後繼節點
waiter:等待鎖的線程
status:節點狀態
節點
節點狀態其實有4種:
取消狀態:status<0;
休眠狀態:status=0;
等待狀態:status=1;
條件等待:status=2;
功能實現
- 鎖類型
- 獨占鎖
- 公平鎖
- 非公平鎖
- 共用鎖
- 公平鎖
- 非公平鎖
獨占鎖acquire(int arg)
以獨占模式獲取,忽略中斷。通過調用至少一次{@link#tryAcquire}來實現,併在成功時返回。否則線程將排隊,可能會重覆阻塞和取消阻塞,調用{@link#tryAcquire},直到成功。此方法可用於實現方法{@link Lock#Lock}。
獨占鎖實現過程
1.tryAcquire
嘗試以獨占模式獲取。此方法應查詢對象的狀態是否允許以獨占模式獲取對象,如果允許,則應獲取對象。
執行acquire的線程總是調用此方法。如果此方法報告失敗,acquire方法可能會將線程排隊(如果尚未排隊),直到其他線程發出釋放信號。這可以用來實現方法{@link Lock#tryLock()}。
這是一個protected方法,在ReentrantLock里實現了FairSync和NonfairSync,也就是公平鎖和非公平鎖。
1-1.FairSync in ReentrantLock
判斷隊列中有沒有其他線程在等待鎖,或者當前線程是第一個在等待鎖的線程,也就是等待隊列中第一個線程,然後CAS嘗試修改鎖狀態,設置當前線程為鎖擁有者。
1-2.NonfairSync in ReentrantLock
不去判斷當前線程是否是等待隊列中的第一個線程,直接CAS嘗試獲取鎖。
2.acquire
在tryAcquire獲取鎖失敗後,acquire主要做的其實是將當前線程放入等待隊列中,然後在迴圈中判斷是否可以參與鎖爭搶。
由於等待隊列優先調度第一個節點進行鎖爭搶,這裡要根據當前線程是否是等待隊列中的第一個節點分情況討論:
當前線程在等待隊列中不存在;
當前線程是等待隊列中第一個節點;
當前線程不是等待隊列中第一個節點;
如果當前節點已存在,且不是頭節點,清理隊列中無效節點,並且節點繼續等待;
如果當前節點已存在,且是頭節點,嘗試再次CAS加鎖;如果加鎖成功,並且是共用鎖,喚醒後繼節點參與鎖爭搶;如果加鎖失敗,該節點進入休眠狀態,設置一個短暫的休眠期,休眠期內無法被喚醒參與鎖爭搶;
如果當前節點不存在,創建節點,放入等待隊列尾部,進行排隊等待鎖爭搶。
如果被中斷,或者在超時時間內沒有獲取到鎖,則加鎖失敗,從等待隊列中清除。
共用鎖acquireShared(int arg)
在共用模式下獲取,忽略中斷。通過至少調用一次{@link#tryAcquireShared}來實現,併在成功時返回。否則線程將排隊,可能會重覆阻塞和取消阻塞,調用{@link#tryAcquireShared},直到成功。
共用鎖跟獨占鎖最大的區別在於,共用鎖可以設置併發數。共用鎖status表示可以同時爭搶鎖的線程數,也就是併發數>1,獨占鎖併發數=1。
共用鎖實現過程
1.tryAcquireShared
這是一個protected方法,在Semaphore里實現了FairSync和NonfairSync,也就是公平鎖和非公平鎖。
1-1.FairSync in Semaphore
如果有前驅節點在等待,返回失敗;
如果剩餘的併發數<0,返回失敗;
1-2.NonfairSync in Semaphore
不去判斷是否有前驅節點在等待,直接根據剩餘的併發數來判斷。
2.acquire
在獨占鎖中分析過,這裡不再贅述。不同點在於共用鎖允許多個線程獲取鎖。
其實到這裡鎖的基本功能已經差不多了,如果只是實現線程之間的互斥,那麼只需要用到加鎖/釋放鎖就夠了。但是如果在互斥的基礎上還要進行線程間的協作,就要用到條件對象了。
條件對象ConditionObject
數據結構
條件對象的主要數據結構就是一個條件隊列,用於存在調用await方法釋放鎖後的線程。
功能實現
await方法說明:
將當前線程放入條件隊列,然後釋放鎖,喚醒等待隊列中的線程參與加鎖。
signal方法說明:
將等待時間最長的線程(如果存在)從該條件的等待隊列移動到擁有鎖的等待隊列。
最後,我想說的是,AQS的實現思想並不僅僅局限於在讀寫鎖中使用,在很多Java中間件、JVM以及操作系統中都有運用,包括其他語言中也有運用。因為這是一個通用的問題,大家都會遇到並且需要解決。我們日常業務開發中也會遇到,只不過需要使用分散式版本的解決方案。