一、核心架構:AQS抽象隊列同步器
二、AQS核心機制
1. 三大核心組件:
state狀態變量:volatile int,表示鎖狀態(0=未鎖定,≥1=鎖定/重入次數)
CLH隊列:雙向鏈表實現的線程等待隊列
Node節點:封裝等待線程和狀態(包含:CANCELLED/SIGNAL/CONDITION/PROPAGATE)
2. 關鍵狀態值:
// Node狀態常量
static final int CANCELLED = 1; // 線程已取消
static final int SIGNAL = -1; // 后繼線程需要喚醒
static final int CONDITION = -2; // 在條件隊列等待
static final int PROPAGATE = -3; // 共享模式下傳播喚醒
三、ReentrantLock工作流程
1. 獲取鎖(lock()):
2. 釋放鎖(unlock()):
四、公平鎖 vs 非公平鎖實現差異
1. 非公平鎖(默認):
final boolean nonfairTryAcquire(int acquires) {// 直接嘗試獲取鎖(可能插隊)if (c == 0 && compareAndSetState(0, acquires)) {setExclusiveOwnerThread(current);return true;}// 檢查重入else if (current == getExclusiveOwnerThread()) {int nextc = c + acquires;setState(nextc);return true;}return false;
}
2. 公平鎖:
protected final boolean tryAcquire(int acquires) {// 先檢查隊列是否有等待線程if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) {setExclusiveOwnerThread(current);return true;}// 重入邏輯相同
}
五、AQS關鍵方法解析
方法 | 作用 |
---|---|
tryAcquire(int) | 嘗試獲取鎖(需子類實現) |
tryRelease(int) | 嘗試釋放鎖(需子類實現) |
acquireQueued() | 線程加入隊列后自旋獲取鎖 |
shouldParkAfterFailedAcquire() | 檢查是否應該阻塞線程 |
unparkSuccessor() | 喚醒后繼節點線程 |
compareAndSetState() | CAS更新state值(保證原子性) |
六、問題總結
Q:ReentrantLock如何基于AQS實現?
A:
ReentrantLock的核心實現依賴于AQS框架:
狀態管理:
使用AQS的
state
變量記錄鎖狀態(0=未鎖定,≥1=重入次數)通過
compareAndSetState()
保證原子更新
線程排隊:
獲取鎖失敗的線程被封裝為Node加入CLH隊列
隊列基于雙向鏈表實現(FIFO)
鎖獲取流程:
先嘗試
tryAcquire()
直接獲取鎖失敗后調用
addWaiter()
加入隊列尾部在隊列中自旋檢查前驅節點狀態
最終通過
LockSupport.park()
阻塞線程
鎖釋放流程:
調用
tryRelease()
減少重入計數當state歸零時,調用
unparkSuccessor()
喚醒隊首線程被喚醒線程重新嘗試獲取鎖
公平性實現:
公平鎖:先檢查隊列是否有等待線程(
hasQueuedPredecessors()
)非公平鎖:允許插隊直接嘗試獲取鎖
Q:AQS為什么使用CLH隊列?
A:
CLH隊列(Craig, Landin, and Hagersten鎖)的優勢:
無鎖入隊:通過CAS操作實現線程安全入隊
低競爭:每個線程只監控前驅節點狀態
高效喚醒:只需修改前驅節點的狀態即可喚醒后繼線程
適應性:完美支持超時、中斷等復雜場景
Q:ReentrantLock如何基于AQS實現?
A:
ReentrantLock的核心實現依賴于AQS框架,未獲取鎖的線程會被完全阻塞:
阻塞時機:當線程嘗試獲取鎖失敗,且自旋檢查后仍無法獲取時
阻塞方式:
調用
LockSupport.park()
方法使線程進入WAITING狀態線程釋放CPU資源,不再消耗計算周期
線程狀態變為
WAITING (parking)
阻塞位置:
線程在CLH隊列中排隊等待
每個線程監控前驅節點的狀態
喚醒機制:
前驅節點釋放鎖時調用
unparkSuccessor()
通過
LockSupport.unpark()
精確喚醒后繼線程喚醒后線程重新嘗試獲取鎖
與忙等待的區別:
不同于忙等待(while循環),park()會使線程讓出CPU
避免空轉消耗CPU資源
通過操作系統級同步原語實現高效阻塞/喚醒
七、進階問題問題
state
變量為什么用volatile?保證多線程間的可見性
確保鎖狀態變化能被立即感知
配合CAS實現無鎖狀態更新
如何處理鎖重入?
// 獲取鎖時檢查當前線程是否是持有者 if (current == getExclusiveOwnerThread()) {int nextc = c + acquires;setState(nextc); // 增加重入計數return true; }
為什么喚醒后繼節點而不是所有線程?
減少不必要的線程喚醒(驚群效應)
保證公平性(FIFO順序)
提高系統吞吐量
AQS如何支持超時機制?
public final boolean tryAcquireNanos(int arg, long nanosTimeout) {if (Thread.interrupted()) throw new InterruptedException();return tryAcquire(arg) || doAcquireNanos(arg, nanosTimeout); // 超時獲取 }
在自旋過程中檢查超時時間
超時后標記節點為CANCELLED
為什么非公平鎖性能更高?
減少線程切換開銷(新線程可直接搶鎖)
避免喚醒延遲(CLH隊列喚醒需要時間)
但可能導致線程饑餓
總結:AQS設計精髓
模板方法模式:定義骨架流程(acquire/release),子類實現關鍵操作(tryAcquire/tryRelease)
無鎖算法:通過CAS實現安全的狀態更新
等待隊列:優雅管理阻塞線程
可擴展性:支持獨占/共享兩種模式