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

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

