首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 綜合教學

AQS 核心源碼深度解析:基於 ReentrantLock 的獨占模式獲取釋放流程全圖解

     編輯:Java 綜合教學

AQS 核心源碼深度解析:基於 ReentrantLock 的獨占模式獲取釋放流程全圖解

AbstractQueuedSynchronizer (AQS) 是 Java 並發包的基石,通過 state 狀態 + FIFO 雙向隊列 + CAS 原子操作 + LockSupport 阻塞喚醒 實現同步器語義。本文以 ReentrantLock 非公平鎖為切入點,深度剖析獨占、非中斷、無超時獲取釋放的核心邏輯。

AQS 核心數據結構

// 同步狀態 (由子類語義賦予:ReentrantLock=重入次數, Semaphore=許可數, CountDownLatch=計數)
private volatile int state;

// 隊列頭尾節點 (虛擬頭節點 dummy + 真實數據節點)
private volatile Node head;
private volatile Node tail;

// 獨占線程標記
private transient Thread exclusiveOwnerThread;

// 節點定義
static final class Node {
    static final Node SHARED = new Node();    // 共享模式標記
    static final Node EXCLUSIVE = null;       // 獨占模式標記
    static final int CANCELLED = 1;           // 取消 (超時/中斷)
    static final int SIGNAL = -1;             // 需喚醒後繼節點
    static final int CONDITION = -2;          // Condition 隊列節點
    static final int PROPAGATE = -3;          // 共享模式傳播

    volatile int waitStatus;      // 節點狀態
    volatile Node prev, next;     // 雙向指針
    volatile Thread thread;       // 關聯線程
    Node nextWaiter;              // Condition 隊列下一節點
}

核心流程:acquire(int arg) 獲取鎖

public final void acquire(int arg) {
    if (!tryAcquire(arg) &&                 // 1. 嘗試直接獲取 (子類實現)
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) // 2. 入隊 + 自旋獲取
        selfInterrupt();                    // 3. 中斷狀態傳播
}

步驟 1:tryAcquire(arg) - 子類嘗試快速獲取

ReentrantLock.NonfairSync 實現:

protected final boolean tryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {                           // 無鎖狀態
        if (compareAndSetState(0, acquires)) { // CAS 設置 state
            setExclusiveOwnerThread(current);
            return true;
        }
    } else if (current == getExclusiveOwnerThread()) { // 重入
        int nextc = c + acquires;
        if (nextc < 0) throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    return false;                           // 獲取失敗,需排隊
}

關鍵點:非公平鎖不檢查隊列,直接 CAS 搶佔;公平鎖會先 hasQueuedPredecessors() 檢查是否有前驅節點。

步驟 2:addWaiter(Node.EXCLUSIVE) - 創建節點入隊

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)) { // CAS 更新尾節點
            pred.next = node;
            return node;
        }
    }
    enq(node);                              // 慢速路徑:自旋入隊
    return node;
}

private Node enq(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;
            }
        }
    }
}

關鍵點:虛擬頭節點 不關聯線程,簡化邊界條件;enq 自旋 CAS 保證線程安全入隊。

步驟 3:acquireQueued(node, arg) - 自旋獲取 + 阻塞

final boolean acquireQueued(Node node, int arg) {
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
            final Node p = node.predecessor();
            // 情況 A:前驅是頭節點,嘗試獲取
            if (p == head && tryAcquire(arg)) {
                setHead(node);              // 成為新頭節點
                p.next = null;              // 斷開舊頭節點引用 (助 GC)
                failed = false;
                return interrupted;
            }
            // 情況 B:需阻塞,先調整前驅狀態
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed) cancelAcquire(node);    // 異常/中斷退出時取消節點
    }
}
shouldParkAfterFailedAcquire(p, node) - 前驅狀態調整
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    int ws = pred.waitStatus;
    if (ws == Node.SIGNAL)                  // 前驅已就緒喚醒後繼
        return true;
    if (ws > 0) {                           // 前驅已取消,跳過失效節點
        do {
            node.prev = pred = pred.prev;
        } while (pred.waitStatus > 0);
        pred.next = node;                   // 重新鏈接
    } else {                                // ws == 0 或 PROPAGATE
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL); // 設置前驅為 SIGNAL
    }
    return false;                           // 重新循環嘗試
}
parkAndCheckInterrupt() - 阻塞當前線程
private final boolean parkAndCheckInterrupt() {
    LockSupport.park(this);                 // 阻塞,等待 unpark
    return Thread.interrupted();            // 返回並清除中斷狀態
}

核心流程:release(int arg) 釋放鎖

public final boolean release(int arg) {
    if (tryRelease(arg)) {                  // 1. 子類嘗試釋放
        Node h = head;
        if (h != null && h.waitStatus != 0) // 2. 頭節點需喚醒後繼
            unparkSuccessor(h);
        return true;
    }
    return false;
}

tryRelease(arg) - 子類釋放邏輯 (ReentrantLock)

protected final boolean tryRelease(int releases) {
    int c = getState() - releases;
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    boolean free = false;
    if (c == 0) {                           // 完全釋放
        free = true;
        setExclusiveOwnerThread(null);
    }
    setState(c);
    return free;                            // 返回是否完全釋放
}

unparkSuccessor(h) - 喚醒後繼節點

private void unparkSuccessor(Node node) {
    int ws = node.waitStatus;
    if (ws < 0)                             // 清除頭節點狀態
        compareAndSetWaitStatus(node, ws, 0);

    Node s = node.next;
    if (s == null || s.waitStatus > 0) {    // 後繼為空或已取消,從尾部向前尋找首個有效節點
        s = null;
        for (Node t = tail; t != null && t != node; t = t.prev)
            if (t.waitStatus <= 0)
                s = t;
    }
    if (s != null)
        LockSupport.unpark(s.thread);       // 喚醒
}

關鍵點:從尾部向前遍歷而非頭部向後,因 next 指針在併發入隊時可能尚未可見,prev 指針設置較早更可靠。

節點取消機制:cancelAcquire(node)

  • 設置 node.waitStatus = CANCELLED
  • 若為尾節點:CAS 更新 tail 為前驅有效節點
  • 若非尾節點:嘗試將前驅 next 指向後繼有效節點
  • 前驅為頭節點或無法設置 SIGNAL 時:調用 unparkSuccessor(node) 確保傳播
  • node.next = node 自引用助 GC

關鍵設計精髓總結

設計點實現方式優勢
虛擬頭節點初始化空 Node 作為 head統一邊界條件,避免頭節點特殊判斷
雙向隊列 + prev 優先入隊設置 prev,釋放從 tail 向前找後繼併發下 prev 可見性優於 next
waitStatus 狀態機CANCELLED(1) / SIGNAL(-1) / CONDITION(-2) / PROPAGATE(-3)少量狀態編碼豐富同步語義
自旋 + 阻塞混合前驅為頭節點時自旋 tryAcquire,否則 park減少上下文切換,適應短暫持鎖場景
中斷狀態傳播acquire 中斷返回 interrupted,調用者 selfInterrupt() 重新設置不吞噬中斷,保持線程中斷語義
模板方法模式AQS 定義骨架 (acquire/release),子類實現 tryAcquire/tryRelease同步邏輯與同步語義解耦

ReentrantLock 非公平 vs 公平鎖差異

  • 非公平:tryAcquire 直接 CAS 搶佔,不檢查隊列,吞吐高但可能餓死
  • 公平:tryAcquire 先 hasQueuedPredecessors() 檢查 head.next != currentThread,有排隊必進隊列,延遲高但公平

源碼閱讀建議:從 ReentrantLock.lock() → sync.acquire(1) → AQS.acquire() 入口,重點掌握 acquireQueued 自旋邏輯、shouldParkAfterFailedAcquire 狀態機、unparkSuccessor 喚醒策略、cancelAcquire 失效清理。畫時序圖輔助理解併發場景下指針變化。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved