AQS,本名:AbstractQueuedSynchronizer,是Java 5引入的一個併發工具類。
它提供了一個基於FIFO(先進先出)隊列,可以用於構建鎖或者其他相關同步裝置的基礎框架。
它的名字翻譯爲抽象隊列同步器,可以分爲三個詞:抽象、隊列、同步器。
正好不知道怎麼開始,那麼現在我們就以名字的三個詞作爲切入點。但是爲了邏輯講得清晰,我調了一下順序:同步器->抽象->隊列。
1、同步器
這個類裏面有一個最關鍵的屬性:state,int類型。我覺得可以理解成兩種意思,首先名稱是state,直接翻譯爲狀態(比如ReentrantLock裏面的state用法),然後類型是int,可以理解爲資源數量(比如Semaphore裏面的state用法)。
它提供了getState和setState方法,還有一個線程安全的compareAndSetState方法。重點是後面這個,利用Unsafe進行CAS操作(如果當前狀態值等於預期值,則以原子方式將同步狀態設置爲給定的更新值,相關資料可以參考上一篇博客:https://blog.csdn.net/qq_31142553/article/details/94407361)。
正是因爲可以做到在併發場景下對state的修改是原子性的並且可以獲取修改結果,所以基於這個特性可以將它做成一些同步器(類比Redis的setNx命令和Zookeeper的創建節點)。
2、抽象
抽象,說明它是可以被子類繼承並且重寫其中的一些方法。官方也是這麼說的:Subclasses must define the protected methods that change this state, and which define what that state means in terms of this object being acquired or released。用我不太擅長的英語翻譯過來就是:子類必須明確更改此狀態的受保護方法,並定義哪種狀態對於此對象意味着被獲取或被釋放。因此這個類提供了以下方法
- tryAcquire(int):試圖在獨佔模式下獲取對象狀態。
- tryRelease(int):試圖設置狀態來反映獨佔模式下的一個釋放。
- tryAcquireShared(int):試圖在共享模式下獲取對象狀態。
- tryReleaseShared(int):試圖設置狀態來反映共享模式下的一個釋放。
- isHeldExclusively():如果對於當前(正調用的)線程,同步是以獨佔方式進行的,則返回 true。
它們的默認實現都是throw new UnsupportedOperationException();要求我們覆蓋這些方法,定義哪種狀態對於此對象意味着被獲取或被釋放。
這就用到了設計模式裏面的模板方法模式:抽象父類定義了一個算法的所有步驟,而將其中一些實現交給子類,以滿足不同的場景需要。
比如用作鎖的話,state可以定義兩個值:0表示未鎖定狀態,1表示鎖定狀態。
那麼加鎖就可以這樣實現
// Acquire the lock if state is zero
public boolean tryAcquire(int acquires) {
assert acquires == 1; // Otherwise unused
if (compareAndSetState(0, 1)) {
return true;
}
return false;
}
釋放鎖可以這樣實現
// Release the lock by setting state to zero
protected boolean tryRelease(int releases) {
assert releases == 1; // Otherwise unused
if (getState() == 0) throw new IllegalMonitorStateException();
setState(0);
return true;
}
比如用作信號量(許可數量)的話,state可以表示剩餘的數量。
那麼獲取所需資源就可以這樣實現
protected int tryAcquireShared(int acquires) {
for (;;) {
int available = getState();
int remaining = available - acquires;
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;
}
}
釋放資源就可以這樣實現
protected final boolean tryReleaseShared(int releases) {
for (;;) {
int current = getState();
int next = current + releases;
if (next < current) // overflow
throw new Error("Maximum permit count exceeded");
if (compareAndSetState(current, next))
return true;
}
}
3、隊列
線程搶奪資源失敗的時候,需要將其放進等待隊列的末尾,然後將其掛起。等到資源釋放的時候,拿出等待隊列裏面的第一個線程,讓其繼續去爭搶資源。
因爲這種首尾都會修改的使用特點,採用鏈表的實現方式遠優於數組。定義了一個Note的內部類表示鏈表的節點,除了有用於維護鏈表連接關係的prev(前節點)和next(後節點)屬性外,還有thread用於保存線程信息、waitStatus表示節點狀態等。
下面是waitStatus的取值,注意默認值是沒有意義的0。
AbstractQueuedSynchronizer中使用head和tail兩個Note類型的屬性存儲鏈表的頭結點和尾節點,從而可以修改或者遍歷那個所謂的隊列(或者說鏈表)。
如果把三者結合起來,就成了下面這個算法。
Acquire:
while (!tryAcquire(arg)) {
enqueue thread if it is not already queued;
possibly block current thread;
}
Release:
if (tryRelease(arg))
unblock the first queued thread;
我們看下ReentrantLock的非公平鎖是怎麼實現的
(1)lock方法
下面看下是怎麼加入等待隊列的(跟着上圖中的acquireQueued(addWaiter(Node.EXCLUSIVE), arg)部分)
(2)unlock方法
文章暫時講到這裏了,還有一些功能暫時沒講到,比如ConditionObject、超時、共享模式等,後續看情況補上吧。
AQS其實蠻複雜的,特別是隊列裏線程等待和喚醒的那部分,如果有講錯的地方,麻煩一定要在評論區裏留言哦,萬分感謝🙏。