AQS資源獲取(獨占模式)
Node節點類
static final class Node {//標記當前節點的線程在共享模式下等待。static final Node SHARED = new Node();//標記當前節點的線程在獨占模式下等待。static final Node EXCLUSIVE = null;//waitStatus的值,表示當前節點的線程已取消(等待超時或被中斷)static final int CANCELLED = 1;//waitStatus的值,表示后繼節點的線程需要被喚醒static final int SIGNAL = -1;//waitStatus的值,表示當前節點在等待某個條件,正處于condition等待隊列中static final int CONDITION = -2;//waitStatus的值,表示在當前有資源可用,能夠執行后續的acquireShared操作static final int PROPAGATE = -3;//等待狀態,值如上,1、-1、-2、-3。volatile int waitStatus;//前趨節點volatile Node prev;//后繼節點volatile Node next;//當前線程volatile Thread thread;//等待隊列中的后繼節點,共享模式下值為SHARED常量Node nextWaiter;//判斷共享模式的方法final boolean isShared() {return nextWaiter == SHARED;}//返回前趨節點,沒有報NPEfinal Node predecessor() throws NullPointerException {Node p = prev;if (p == null)throw new NullPointerException();elsereturn p;}//下面是三個構造方法Node() {} // Used to establish initial head or SHARED markeNode(Thread thread, Node mode) { // Used by addWaiterthis.nextWaiter = mode;this.thread = thread;}Node(Thread thread, int waitStatus) { // Used by Conditionthis.waitStatus = waitStatus;this.thread = thread;}
}
嘗試獲取資源,方法分析
public final void acquire(int arg) {if (!tryAcquire(arg) &&acquireQueued(addWaiter(Node.EXCLUSIVE), arg))selfInterrupt();}
獲取失敗調用addWaiter將當前線程封裝成獨占模式的節點,添加到AQS隊列尾部
// mode 獨占模式 共享模式private Node addWaiter(Node mode) {Node node = new Node(Thread.currentThread(), mode);// 獲取到尾節點Node pred = tail;// 尾節點不為空if (pred != null) {// 新節點,跟在尾節點后,新節點的前驅指向獲取到的尾節點node.prev = pred;// 新節點設置為尾節點if (compareAndSetTail(pred, node)) {// 剛剛獲取的尾節點的后繼節點指向新的節點,新節點成為最終尾節點,添加到隊列尾部pred.next = node;return node;}}// 隊列沒有節點,直接加入隊列enq(node);return node;}// 入隊方法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;}}}}
通過addWaiter已經將當前線程封裝成獨占模式的 Node 節點,并成功放入隊列尾部。接下來會調用acquireQueued方法在等待隊列中排隊
final boolean acquireQueued(final Node node, int arg) {
// 獲取資源失敗標識boolean failed = true;try {// 線程是否被中斷標識boolean interrupted = false;// 自旋 掛起for (;;) {// 前驅節點final Node p = node.predecessor();// 是否頭節點,再次獲取鎖成功if (p == head && tryAcquire(arg)) {// 當前節點設為頭節點setHead(node);// 斷掉引用p.next = null; // help GC 頭節點出列failed = false;return interrupted;}// 如果不是頭節點或獲取鎖失敗 準備阻塞if (shouldParkAfterFailedAcquire(p, node) &&parkAndCheckInterrupt())interrupted = true;}} finally {if (failed)// 取消同步狀態cancelAcquire(node);}}//將當前節點設置為頭節點
private void setHead(Node node) {head = node;node.thread = null;node.prev = null;
}
// 判斷當前線程是否可以進入waiting狀態
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
// 獲取前驅節點的等待狀態int ws = pred.waitStatus;if (ws == Node.SIGNAL) // 可以被喚醒return true;if (ws > 0) { // 表示當前線程被取消do {// 關鍵 節點一直往前移動,直到找到狀態<=0的節點node.prev = pred = pred.prev;} while (pred.waitStatus > 0);pred.next = node;} else {// 下面節點進來的條件,前驅節點是SIGNAL,這里設置為SIGNALcompareAndSetWaitStatus(pred, ws, Node.SIGNAL);}return false;}// 掛起線程private final boolean parkAndCheckInterrupt() {LockSupport.park(this);return Thread.interrupted();
}protected final boolean tryAcquire(int acquires) {final Thread current = Thread.currentThread();// 判斷是否0int c = getState();if (c == 0) {if (!hasQueuedPredecessors() &&compareAndSetState(0, acquires)) {// 設為當前線程setExclusiveOwnerThread(current);return true;}}// 不為0 嘗試獲取鎖else if (current == getExclusiveOwnerThread()) {int nextc = c + acquires;if (nextc < 0)throw new Error("Maximum lock count exceeded");setState(nextc);return true;}return false;
}
獲取資源的整體流程圖如下:
AQS資源獲取(獨占模式)特點
1.互斥訪問(Mutual Exclusion)
- 單線程持有:同一時間只允許一個線程持有資源
- 狀態管理:通過 state 變量(volatile int)表示資源狀態
- CAS操作:使用 compareAndSetState 確保狀態更新原子性
- 示例:ReentrantLock 中 state=0 表示未鎖定,state>0 表示鎖定狀態
2. 線程阻塞隊列(CLH Queue)
- FIFO隊列:使用雙向鏈表實現的 CLH 變體隊列
- 節點類型:Node.EXCLUSIVE 表示獨占模式節點
- 排隊機制:獲取資源失敗的線程會被封裝為節點加入隊列尾部
3. 可重入支持(Reentrancy)
- 重入計數:state 變量記錄重入次數
- 持有線程:通過 exclusiveOwnerThread 記錄當前持有線程
- 示例:ReentrantLock 允許線程多次獲取同一把鎖
AQS資源釋放(獨占模式)
public void unlock() {sync.release(1);}public final boolean release(int arg) {if (tryRelease(arg)) {Node h = head;if (h != null && h.waitStatus != 0)// 喚醒后繼節點unparkSuccessor(h);return true;}return false;}// 喚醒后繼節點的線程,傳入節點private void unparkSuccessor(Node node) {// 獲取當前節點的等待狀態int ws = node.waitStatus;if (ws < 0)// <0 嘗試設置為0node.compareAndSetWaitStatus(ws, 0);// 獲取節點后繼Node s = node.next;// 后繼節點為空或等待狀態>0 節點取消if (s == null || s.waitStatus > 0) {s = null;// 從尾部向前遍歷for (Node p = tail; p != node && p != null; p = p.prev)if (p.waitStatus <= 0)s = p;}// 不為空,準備進行喚醒操作if (s != null)// 線程停止阻塞LockSupport.unpark(s.thread);}
AQS 資源釋放(獨占模式)流程圖
AQS資源釋放(獨占模式)特點
1.狀態更新:
- 通過 tryRelease 更新同步狀態
- 清除當前持有線程
2.喚醒策略:
- 只喚醒頭節點的下一個有效節點
- 采用從后向前查找策略解決并發入隊問題
3.線程安全:
- 使用 CAS 更新 waitStatus
- 無鎖化設計確保高性能
4.取消處理:
- 自動跳過已取消節點(waitStatus > 0)
- 確保喚醒的節點都是有效等待節點