目錄
1. AQS 的定位和作用
2. AQS 的核心結構
3. acquire / release 模板方法
4. 條件隊列 ConditionObject
5. 自旋 + park 機制
6. Unsafe 與 AQS
7. 同步器實現示例
8.AOS
1. AQS 的定位和作用
AbstractQueuedSynchronizer(簡稱 AQS)是 Java 併發包中用於構建各種同步器的框架,它本身不提供鎖,而提供一個可重用的隊列化同步器模板。
核心目標:
- 提供 FIFO 等待隊列(基於 CLH 鎖思想)。
- 封裝線程掛起/喚醒機制(自旋 + park/unpark)。
- 讓開發者只需實現資源獲取與釋放的邏輯(
tryAcquire/tryRelease等)。
應用實例:
ReentrantLock(獨佔鎖)Semaphore(計數信號量)CountDownLatch(倒計時器)ReentrantReadWriteLock(讀寫鎖)
2. AQS 的核心結構
public abstract class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer {
private transient volatile Node head; // 隊列頭
private transient volatile Node tail; // 隊列尾
private volatile int state; // 同步狀態
static final int WAITING = 1; // 線程等待
static final int CANCELLED = 0x80000000; // 取消等待
static final int COND = 2; // 條件等待
}
2.1 Node 節點
- CLH 隊列節點,關聯一個線程。
- Node 的狀態:
WAITING、CANCELLED、COND。 - 三種子類:
ExclusiveNode:排他鎖SharedNode:共享鎖ConditionNode:條件隊列節點
2.2 隊列管理
head:隊列頭(虛擬節點,不實際佔用資源)。tail:隊列尾(新線程入隊)。- 當線程競爭失敗時,會 封裝成 Node 入隊,排隊等待喚醒。
隊列特點:
- FIFO 隊列,保證公平性(非公平鎖除外)。
- 通過 prev/next 指針串聯。
- 掛起線程不消耗 CPU。
3. acquire / release 模板方法
3.1 acquire(獲取鎖)
流程:
- 嘗試獲取鎖:調用子類
tryAcquire或tryAcquireShared。 - 如果失敗:
- 封裝當前線程為 Node。
- 入隊到 CLH 隊列。
- 自旋幾次。
- 自旋失敗 →
park()掛起。
- 被喚醒後:
- 清理狀態。
- 再次嘗試獲取鎖。
- 獲取鎖成功 → 節點成為 head。
注意:自旋 + park 是 AQS 的 CPU 友好策略:短時間可自旋,長時間掛起,不浪費資源。
當線程嘗試獲取鎖失敗時,AQS 並不是立即把線程掛起(park),而是先自旋幾次,這個策略叫 自旋等待(spin-wait)。原因有幾個:
- 短時間鎖衝突,直接自旋比掛起效率高
- 如果鎖很快就會被釋放,比如持有鎖的線程只執行了幾行代碼,線程 park 再 unpark 的開銷比直接忙等(自旋)要高。
- 自旋幾次能快速拿到鎖,減少線程切換開銷。
- 減少上下文切換
- park/unpark 會涉及操作系統調度,切換線程狀態,比較昂貴。
- 自旋在 CPU 層面循環檢查,不切換線程,短時間衝突非常高效。
- 避免“虛假喚醒”開銷
- 自旋可以讓後續競爭的線程更快地接替鎖,而不是頻繁 park/unpark。
3.2 release(釋放鎖)
流程:
- 調用子類
tryRelease修改state。 - 調用
signalNext(head):
- 喚醒 head 後繼節點。
- 通過
LockSupport.unpark(node.waiter)恢復線程運行。
- 等待隊列中的線程重新競爭鎖。
4. 條件隊列 ConditionObject
- AQS 提供了
ConditionObject,是 唯一實現了Condition接口的類。 - 條件隊列用於
await()/signal()操作。
await() 流程:
- 封裝線程為
ConditionNode,放入條件隊列。 - 釋放鎖(
enableWait保存 state,釋放資源)。 - 阻塞線程:park。
- 被
signal()喚醒後:
- 從條件隊列移回 AQS 隊列。
- 重新競爭鎖(acquire)。
ConditionObject 與 CLH 隊列協作,實現了 線程等待通知模式。
需要注意,進入條件隊列的線程必須是持有鎖的,換句話説也就是進入await()的線程必須尺有所(例如ReentrantLock),await() 的設計思想:線程等待某個條件,在等待時必須釋放鎖,讓其他線程可以修改條件狀態。
signal() 流程:
- 從條件隊列取出節點。
- 移入 AQS CLH 隊列。
- 喚醒該節點。
5. 自旋 + park 機制
- 自旋 (
Thread.onSpinWait):
- 短時間競爭鎖,CPU 快速輪詢。
- park / unpark (
LockSupport.park/unpark):
- 長時間等待,釋放 CPU。
- 被喚醒後,重新進入競爭。
這也是為什麼 acquire 中會有:失敗 → 入隊 → 自旋幾次 → park。
6. Unsafe 與 AQS
Unsafe 是 JDK 內部的低級工具類,提供:
- CAS 操作(compareAndSwapInt / compareAndSwapObject)
- 直接內存操作
- 線程阻塞/喚醒底層操作
在 AQS 的作用
- 修改
state(同步狀態)使用 CAS。 - 修改
head/tail指針使用 CAS。 - 保證多線程下 原子性。
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long stateOffset = unsafe.objectFieldOffset(AbstractQueuedSynchronizer.class.getDeclaredField("state"));
compareAndSetState實現依賴 Unsafe CAS。- CLH 隊列節點入隊/出隊也依賴 Unsafe CAS 保證原子更新。
AQS 之所以高效和線程安全,很大程度上依賴 Unsafe 提供的底層原子操作。
7. 同步器實現示例
ReentrantLock(可重入獨佔鎖)
- state:表示鎖的持有次數
- 獨佔模式(Exclusive)
|
方法
|
實現邏輯
|
説明
|
|
tryAcquire(int acquires)
|
state == 0 → CAS 置 1state > 0 && 當前線程 == owner → state++
|
如果 state 為 0,嘗試獲取鎖,否則檢查是否可重入
|
|
tryRelease(int releases)
|
state--state==0 → 釋放鎖
|
當最後一次釋放時喚醒等待線程
|
|
isHeldExclusively()
|
return 當前線程 == owner
|
判斷鎖是否被當前線程持有
|
流程:
- 線程調用
lock()→ 調用 AQS acquire。 - acquire 調用 tryAcquire,獲取鎖成功返回;失敗 → 入隊 → 自旋 → park。
- unlock 調用 release,state 減 1,state==0 → signal 下一個節點。
Semaphore(信號量,共享鎖)
- state:可用許可數量
- 共享模式(Shared)
|
方法
|
實現邏輯
|
説明
|
|
tryAcquireShared(int acquires)
|
state > 0 → state -= acquires → 返回正數
|
獲取許可成功
|
|
tryReleaseShared(int releases)
|
state += releases → signalNext
|
釋放許可,並喚醒條件隊列線程
|
流程:
- acquire() 調用 tryAcquireShared,如果失敗 → 入隊 → park 等待。
- release() 增加許可,喚醒隊列頭線程。
CountDownLatch(倒計時鎖,共享鎖)
- state:倒計時初始值
- 共享模式(Shared)
|
方法
|
實現邏輯
|
説明
|
|
tryAcquireShared(int acquires)
|
state == 0 → 返回 1state > 0 → 返回 -1
|
只有倒計時歸零的線程才能繼續
|
|
tryReleaseShared(int releases)
|
state-- → state==0 → signalAll
|
倒計時歸零喚醒所有等待線程
|
特點:
- 所有等待線程都共享鎖,直到倒計時完成。
ReentrantReadWriteLock(讀寫鎖)
- state:高16位讀鎖計數 + 低16位寫鎖計數
- 共享鎖(讀鎖):tryAcquireShared
- 獨佔鎖(寫鎖):tryAcquire
|
方法
|
實現邏輯
|
説明
|
|
tryAcquireShared
|
state 高16位 < max → state += 1
|
獲取讀鎖,允許多個讀線程
|
|
tryReleaseShared
|
state 高16位--
|
釋放讀鎖,必要時喚醒寫鎖線程
|
|
tryAcquire
|
state == 0 或 當前線程 == owner → CAS 設置
|
寫鎖獨佔,支持重入
|
|
tryRelease
|
state-- → 0 → 清空 owner
|
寫鎖釋放後喚醒隊列頭線程
|
示例對比
|
同步器
|
鎖類型
|
state 含義
|
獲取鎖方式
|
釋放鎖方式
|
|
ReentrantLock
|
獨佔
|
持有次數
|
tryAcquire
|
tryRelease
|
|
Semaphore
|
共享
|
可用許可數
|
tryAcquireShared
|
tryReleaseShared
|
|
CountDownLatch
|
共享
|
倒計時
|
tryAcquireShared
|
tryReleaseShared
|
|
ReentrantReadWriteLock
|
共享/獨佔
|
高16位讀,低16位寫
|
tryAcquire / tryAcquireShared
|
tryRelease / tryReleaseShared
|
可以看出 AQS 的模式非常統一:
- 獨佔鎖 → tryAcquire / tryRelease
- 共享鎖 → tryAcquireShared / tryReleaseShared
- 所有同步器共用 FIFO 隊列 + park/unpark + CAS 操作
看到這裏有讀者好奇,AQS好像沒有維護一個變量來標記當前是哪個線程持有鎖,那例如像ReentrantLock是怎麼判斷是不是當前線程持有鎖的呢?
8.AOS作用(維護哪個線程持有資源|鎖)
想要知道當前哪個線程持有鎖,就要藉助AOS
AQS 本身不直接維護線程歸屬
在 AQS 裏,核心字段主要有:
private volatile int state; // 同步狀態
private transient volatile Node head; // 隊列頭
private transient volatile Node tail; // 隊列尾
state:表示資源佔用狀態(獨佔鎖的次數 / 信號量許可數)head/tail:表示等待隊列- AQS 沒有直接保存哪個線程持有鎖的信息
所以,單靠 AQS,是無法直接判斷“當前線程是不是持有鎖”的。
AbstractOwnableSynchronizer(AOS)提供了線程歸屬
private transient Thread exclusiveOwnerThread;
protected final void setExclusiveOwnerThread(Thread t) { ... }
protected final Thread getExclusiveOwnerThread() { ... }
- AOS 只保存 獨佔模式下的擁有者線程
- AQS 繼承了 AOS,所以 AQS 可以 利用
exclusiveOwnerThread來記錄持有鎖的線程 - 注意:state 和 exclusiveOwnerThread 是配合使用的
- state > 0 → 表示鎖被佔用
- exclusiveOwnerThread → 表示是哪個線程佔用
ReentrantLock 如何判斷“自己是否持有鎖”
以 ReentrantLock.Sync 為例:
protected final boolean tryAcquire(int acquires) {
Thread current = Thread.currentThread();
int c = getState();
if (c == 0) { // 鎖未被佔用
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current); // 記錄持有鎖線程
return true;
}
} else if (current == getExclusiveOwnerThread()) { // 可重入判斷
int nextc = c + acquires;
setState(nextc);
return true;
}
return false;
}
解釋:
- 鎖空閒時
- 嘗試通過 CAS 搶佔 state
- 搶到鎖後,通過
setExclusiveOwnerThread(current)記錄當前線程
- 鎖已被佔用時
- 判斷
current == getExclusiveOwnerThread() - 如果是當前線程,就允許重入(state++)
所以判斷線程是否持有鎖,完全依賴 AOS 的 exclusiveOwnerThread,而不是隊列 head/tail。
AQS + AOS 關係總結
|
類
|
作用
|
|
AbstractOwnableSynchronizer
|
保存獨佔鎖持有者線程 ( |
|
AbstractQueuedSynchronizer
|
維護 state + CLH 隊列 + 提供 acquire/release 模板
|
|
ReentrantLock.Sync
|
實現 tryAcquire/tryRelease、可重入邏輯,操作 state + exclusiveOwnerThread
|
9.共享模式怎麼維護線程歸屬?
上文也説了AOS 只記錄獨佔模式下的持有線程,那共享模式例如CountDownLatch、Semaphore這種怎麼判斷線程歸屬呢?
答案就是不用記錄線程歸屬,也就是説沒有方法能之間判斷改線程共享模式下是否持有鎖(或許説沒有鎖)
其實這種模式下
- 資源可以被多個線程共享訪問(共享計數)
- 不關心“誰”佔用資源,只關心 state 是否允許訪問 實現:
- state 記錄資源狀態(如
CountDownLatch的計數器) - 線程調用 acquireShared() → 嘗試獲取 state
- state <= 0 → 阻塞等待;state > 0 → 獲取成功
注意
- 共享模式下沒有
exclusiveOwnerThread的意義 - 沒有 API 可以問“當前線程持有鎖嗎”,因為本身沒有鎖歸屬概念