hello啊,各位觀眾姥爺們!!!本baby今天又來報道了!哈哈哈哈哈嗝🐶
面試官:詳細說說AQS
AQS(AbstractQueuedSynchronizer)是 Java 并發包(java.util.concurrent.locks
)的核心基礎框架,用于構建鎖和其他同步器(如 ReentrantLock
、Semaphore
、CountDownLatch
等)。它通過模板方法模式定義了一套多線程訪問共享資源的同步機制,開發者只需繼承 AQS 并實現特定方法,即可自定義同步器。
AQS 的核心設計思想
AQS 的核心是 “同步狀態管理” + “線程排隊機制”:
-
同步狀態(
state
字段):
通過一個volatile int state
字段表示共享資源的狀態(如鎖是否被占用、信號量剩余許可數等)。- 開發者需根據同步器的語義定義
state
的用途(例如:ReentrantLock
用state
表示重入次數)。
- 開發者需根據同步器的語義定義
-
CLH 隊列(線程等待隊列):
采用 CLH 變體的雙向鏈表隊列,將未獲取到資源的線程封裝為Node
節點并排隊等待。- CLH 隊列的每個節點保存了前驅和后繼節點的引用,以及線程的狀態(如是否被取消)。
-
模板方法模式:
AQS 提供通用的排隊和阻塞機制,開發者只需實現以下關鍵方法(需保證線程安全):tryAcquire(int arg)
:嘗試以獨占模式獲取資源。tryRelease(int arg)
:嘗試釋放獨占模式的資源。tryAcquireShared(int arg)
:嘗試以共享模式獲取資源。tryReleaseShared(int arg)
:嘗試釋放共享模式的資源。isHeldExclusively()
:判斷當前線程是否獨占資源。
AQS 的內部結構
-
state
字段private volatile int state; // 核心狀態變量
- 通過
getState()
,setState()
,compareAndSetState()
方法原子操作狀態。
- 通過
-
CLH 隊列
// 隊列頭尾指針 private transient volatile Node head; private transient volatile Node tail;// Node 節點結構(每個等待線程封裝為一個 Node) static final class Node {volatile int waitStatus; // 等待狀態(如 CANCELLED、SIGNAL)volatile Node prev; // 前驅節點volatile Node next; // 后繼節點volatile Thread thread; // 關聯的線程Node nextWaiter; // 條件隊列的下一節點(用于 Condition) }
-
兩種模式
- 獨占模式(Exclusive):資源一次只能被一個線程占用(如
ReentrantLock
)。 - 共享模式(Shared):資源可被多個線程共享(如
Semaphore
、CountDownLatch
)。
- 獨占模式(Exclusive):資源一次只能被一個線程占用(如
AQS 的工作流程
1. 獲取資源(以獨占模式為例)
-
步驟:
- 調用
acquire(int arg)
方法。 - 先嘗試
tryAcquire(arg)
(需開發者實現),若成功則直接返回。 - 若失敗,將線程封裝為
Node
加入 CLH 隊列尾部,并通過acquireQueued()
自旋或阻塞等待。 - 在隊列中等待的線程會不斷檢查前驅節點是否為頭節點,若是則再次嘗試獲取資源。
- 調用
-
關鍵代碼邏輯:
public final void acquire(int arg) {if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))selfInterrupt(); // 恢復中斷狀態 }
2. 釋放資源
-
步驟:
- 調用
release(int arg)
方法。 - 先嘗試
tryRelease(arg)
(需開發者實現),若成功則喚醒隊列中的后繼節點。
- 調用
-
關鍵代碼邏輯:
public final boolean release(int arg) {if (tryRelease(arg)) {Node h = head;if (h != null && h.waitStatus != 0)unparkSuccessor(h); // 喚醒后繼節點的線程return true;}return false; }
3. 中斷與超時
- AQS 支持可中斷的獲取資源(
acquireInterruptibly()
)和超時獲取(tryAcquireNanos()
)。 - 若線程在等待中被中斷或超時,會移除對應的節點并拋出
InterruptedException
。
AQS 的應用場景
-
ReentrantLock
- 通過實現
tryAcquire
和tryRelease
方法,管理鎖的重入計數(state
字段記錄持有鎖的線程和重入次數)。
- 通過實現
-
Semaphore
- 使用
state
表示剩余許可數,tryAcquireShared
嘗試獲取許可,tryReleaseShared
釋放許可。
- 使用
-
CountDownLatch
state
初始化為計數器值,await()
調用acquireShared
,countDown()
調用releaseShared
。
-
ReentrantReadWriteLock
- 將
state
的高 16 位用于讀鎖(共享模式),低 16 位用于寫鎖(獨占模式)。
- 將
AQS 的設計優勢
-
靈活性
開發者只需關注資源狀態的管理(state
操作),無需處理線程排隊、阻塞與喚醒等底層邏輯。 -
高效性
- CLH 隊列的變體設計減少了鎖競爭,通過自旋和 CAS 操作提升性能。
- 雙向鏈表便于處理取消和超時的節點。
-
可擴展性
支持獨占和共享模式,且可通過Condition
實現更復雜的線程通信(如生產者-消費者模型)。
AQS 的關鍵源碼技巧
-
自旋 + CAS
AQS 大量使用 CAS(Unsafe
類)和自旋操作,例如入隊時通過 CAS 保證線程安全:private Node enq(final Node node) {for (;;) { // 自旋直到成功Node t = tail;if (t == null) { // 隊列為空,初始化頭節點if (compareAndSetHead(new Node()))tail = head;} else {node.prev = t;if (compareAndSetTail(t, node)) {t.next = node;return t;}}} }
-
線程阻塞與喚醒
使用LockSupport.park()
和LockSupport.unpark(thread)
控制線程的阻塞與喚醒,避免優先級反轉問題。
技術資料大全:https://pan.q刪掉漢子uark.cn/s/aa7f2473c65b
總結
AQS 是 Java 并發包的基石,通過統一的框架解決了同步器的核心問題:
- 狀態管理:通過
state
字段靈活表示資源狀態。 - 線程排隊:CLH 隊列高效管理等待線程。
- 模板方法:分離通用邏輯與具體實現,降低開發復雜度。
理解 AQS 是掌握 Java 并發編程的關鍵,但實際開發中建議優先使用 Java 內置的同步器(如 ReentrantLock
),而非直接繼承 AQS。