Java中的AQS入門攻略
一、AQS 是什么?
AQS,全稱 AbstractQueuedSynchronizer,即抽象隊列同步器。
- 抽象:它是一個抽象類,本身不能直接實例化,需要子類去繼承它,并實現(xiàn)其保護方法來管理同步狀態(tài)。
- 隊列:它內部維護了一個先進先出(FIFO)的等待隊列,用于存放那些沒有搶到鎖的線程。
- 同步器:它是構建鎖和其他同步組件(如 Semaphore、CountDownLatch 等)的基礎框架。
核心思想:
AQS 使用一個整型的 volatile 變量(state) 來表示同步狀態(tài)(例如,鎖被重入的次數(shù)、許可的數(shù)量等),并通過一個內置的 FIFO 隊列來完成資源獲取線程的排隊工作。
設計模式:
AQS 是 模板方法模式 的經典應用。父類(AQS)定義了骨架和核心算法,而將一些關鍵的操作以 protected 方法的形式留給子類去實現(xiàn)。這樣,實現(xiàn)一個自定義同步器只需要關注如何管理 state 狀態(tài)即可,至于線程的排隊、等待、喚醒等復雜操作,AQS 已經幫我們完成了。
二、AQS 的核心結構
AQS 的核心可以概括為三部分:同步狀態(tài)(state)、等待隊列 和 條件隊列。
1. 同步狀態(tài)(State)
這是一個 volatile int 類型的變量,是 AQS 的靈魂。
private volatile int state;
它的具體含義由子類決定,非常靈活:
- 在 ReentrantLock 中,
state表示鎖被同一個線程重復獲取的次數(shù)。state=0表示鎖空閑,state=1表示鎖被占用,state>1表示鎖被重入。 - 在 Semaphore 中,
state表示當前可用的許可數(shù)量。 - 在 CountDownLatch 中,
state表示計數(shù)器當前的值。
對 state 的操作是原子的,通過 getState(), setState(int newState), compareAndSetState(int expect, int update) 等方法進行。
2. 等待隊列(CLH 隊列的變體)
這是一個雙向鏈表,是 AQS 實現(xiàn)阻塞鎖的關鍵。當線程請求共享資源失敗時,AQS 會將當前線程以及等待狀態(tài)等信息構造成一個節(jié)點(Node) 并將其加入隊列的尾部,同時阻塞該線程。
- 頭節(jié)點(Head):指向獲取到資源的線程所在的節(jié)點。頭節(jié)點不持有線程,是一個“虛節(jié)點”。
- 尾節(jié)點(Tail):指向隊列中最后一個節(jié)點。
當一個線程釋放資源時,它會喚醒后繼節(jié)點,后繼節(jié)點成功獲取資源后,會將自己設置為新的頭節(jié)點。
主要原理圖如下:

AQS 使用一個 Volatile 的 int 類型的成員變量來表示同步狀態(tài),通過內置的 FIFO 隊列來完成資源獲取的排隊工作,通過 CAS 完成對 State 值的修改。
3. 條件隊列(Condition Object)
AQS 內部類 ConditionObject 實現(xiàn)了 Condition 接口,用于支持 await/signal 模式的線程間協(xié)作。每個 ConditionObject 對象都維護了一個自己的單向鏈表(條件隊列)。
- 當線程調用
Condition.await()時,會釋放鎖,并將當前線程構造成節(jié)點加入條件隊列,然后阻塞。 - 當線程調用
Condition.signal()時,會將條件隊列中的第一個等待節(jié)點轉移到 AQS 的等待隊列中,等待重新獲取鎖。
注意:一個 AQS 實例可以對應多個 Condition 對象(即多個條件隊列),但只有一個等待隊列。
三、AQS 的設計與關鍵方法
AQS 將資源獲取的方式分為兩種:
- 獨占模式(Exclusive):一次只有一個線程能執(zhí)行,如 ReentrantLock。
- 共享模式(Shared):多個線程可以同時執(zhí)行,如 Semaphore、CountDownLatch。
AQS 提供了頂層的入隊和出隊邏輯,而將嘗試獲取資源和嘗試釋放資源的具體策略留給了子類。
需要子類重寫的關鍵方法(Protected)
這些方法在 AQS 中是 protected 的,默認拋出 UnsupportedOperationException。
獨占模式:
boolean tryAcquire(int arg):嘗試以獨占方式獲取資源。成功返回 true,失敗返回 false。boolean tryRelease(int arg):嘗試以獨占方式釋放資源。成功返回 true,失敗返回 false。
共享模式:
int tryAcquireShared(int arg):嘗試以共享方式獲取資源。負數(shù)表示失?。? 表示成功,但后續(xù)共享獲取可能失?。徽龜?shù)表示成功,且后續(xù)共享獲取可能成功。boolean tryReleaseShared(int arg):嘗試以共享方式釋放資源。
其他:
boolean isHeldExclusively():當前同步器是否在獨占模式下被線程占用。在 Condition 相關操作中會用到。
供外部調用的重要方法(Public)
這些是模板方法,子類一般不重寫,使用者(或子類)直接調用。
獨占模式:
void acquire(int arg):以獨占模式獲取資源,忽略中斷。如果獲取失敗,會進入等待隊列。void acquireInterruptibly(int arg):同上,但響應中斷。boolean tryAcquireNanos(int arg, long nanosTimeout):在acquireInterruptibly基礎上增加了超時限制。boolean release(int arg):以獨占模式釋放資源。
共享模式:
void acquireShared(int arg):以共享模式獲取資源。void acquireSharedInterruptibly(int arg):響應中斷的共享獲取。boolean tryAcquireSharedNanos(int arg, long nanosTimeout):帶超時的共享獲取。boolean releaseShared(int arg):以共享模式釋放資源。
四、源碼級工作流程解析(以acquire為例)
我們來看一下最核心的 acquire 方法,它展示了 AQS 的完整工作流程:
public final void acquire(int arg) {
if (!tryAcquire(arg) && // 1. 嘗試直接獲取資源(子類實現(xiàn))
acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) // 2. 獲取失敗,則加入隊列;3. 在隊列中自旋/阻塞等待
selfInterrupt(); // 如果在等待過程中被中斷,補上中斷標記
}tryAcquire(arg):- 這是子類實現(xiàn)的方法。比如在 ReentrantLock 的非公平鎖實現(xiàn)中,它會直接嘗試使用 CAS 修改
state,如果成功,就將當前線程設置為獨占線程。 - 如果
tryAcquire成功,整個acquire方法就結束了,線程繼續(xù)執(zhí)行。 - 如果失敗,進入下一步。
- 這是子類實現(xiàn)的方法。比如在 ReentrantLock 的非公平鎖實現(xiàn)中,它會直接嘗試使用 CAS 修改
addWaiter(Node.EXCLUSIVE):- 創(chuàng)建一個代表當前線程的 Node 節(jié)點,模式為獨占模式(Node.EXCLUSIVE)。
- 通過 CAS 操作,快速地將這個新節(jié)點設置為尾節(jié)點。如果失敗,則進入
enq(node)方法,通過自旋 CAS 的方式確保節(jié)點被成功添加到隊列尾部。
acquireQueued(final Node node, int arg):- 這是核心中的核心。節(jié)點入隊后,會在這個方法里進行自旋(循環(huán))等待。
- 在循環(huán)中,它會檢查自己的前驅節(jié)點是不是頭節(jié)點(
p == head)。如果是,說明自己是隊列中第一個等待的線程,會再次調用tryAcquire嘗試獲取資源(因為此時鎖可能剛好被釋放了,這是一個避免不必要的線程掛起、提高性能的優(yōu)化)。 - 如果獲取成功,就將自己設為新的頭節(jié)點,然后返回。
- 如果前驅不是頭節(jié)點,或者再次嘗試獲取失敗,則會調用
shouldParkAfterFailedAcquire方法,檢查并更新前驅節(jié)點的狀態(tài)(比如將其waitStatus設置為SIGNAL,表示“當你釋放鎖時,需要喚醒我”)。 - 如果一切就緒,就調用
parkAndCheckInterrupt()方法,使用LockSupport.park(this)阻塞(掛起)當前線程。 - 當線程被喚醒后(通常是由前驅節(jié)點釋放鎖時
unpark的),會再次檢查自己是否是頭節(jié)點的后繼,并重復上述自旋過程,直到成功獲取資源。
selfInterrupt():- 如果在等待過程中線程被中斷,
acquireQueued方法會返回true,這里會調用selfInterrupt補上中斷標志,因為 AQS 在acquire過程中是忽略中斷的。
- 如果在等待過程中線程被中斷,
釋放流程(release)相對簡單:
public final boolean release(int arg) {
if (tryRelease(arg)) { // 1. 子類嘗試釋放資源
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h); // 2. 喚醒后繼節(jié)點
return true;
}
return false;
}unparkSuccessor 會找到隊列中第一個需要喚醒的線程(通常是頭節(jié)點的下一個有效節(jié)點),然后調用 LockSupport.unpark(s.thread) 將其喚醒。
五、AQS 的應用舉例
AQS 是 JUC 包的基石,幾乎所有的同步工具都基于它:
- ReentrantLock:使用 AQS 的獨占模式,
state表示重入次數(shù)。 - ReentrantReadWriteLock:讀寫鎖。AQS 的
state高16位表示讀鎖狀態(tài),低16位表示寫鎖狀態(tài)。 - Semaphore:使用 AQS 的共享模式,
state表示可用許可數(shù)。 - CountDownLatch:使用 AQS 的共享模式,
state表示計數(shù)器值。countDown()是releaseShared,await()是acquireShared。 - ThreadPoolExecutor:其內部的工作線程
Worker類,也繼承了 AQS,用于實現(xiàn)獨占鎖,來判斷線程是否空閑。
六、總結
AQS 的核心貢獻在于,它提供了一個強大的框架,將復雜的線程排隊、阻塞、喚醒等底層操作封裝起來,讓同步器的開發(fā)者只需要關注一個核心問題:如何管理那個 state 變量。
它的優(yōu)點:
- 極大地降低了構建鎖和同步器的復雜度。
- 性能高效:通過自旋、CAS 等無鎖編程技術,減少了線程上下文切換的開銷。
- 靈活強大:通過兩種模式的區(qū)分,可以構建出各種復雜的同步工具。
到此這篇關于Java中的AQS入門攻略的文章就介紹到這了,更多相關java aqs是什么內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

