目錄

1. AQS 的定位和作用

2. AQS 的核心結構

3. acquire / release 模板方法

4. 條件隊列 ConditionObject

5. 自旋 + park 機制

6. Unsafe 與 AQS

7. 同步器實現示例

8.AOS



1. AQS 的定位和作用

AbstractQueuedSynchronizer(簡稱 AQS)是 Java 併發包中用於構建各種同步器的框架,它本身不提供鎖,而提供一個可重用的隊列化同步器模板

核心目標:

  1. 提供 FIFO 等待隊列(基於 CLH 鎖思想)。
  2. 封裝線程掛起/喚醒機制(自旋 + park/unpark)。
  3. 讓開發者只需實現資源獲取與釋放的邏輯(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 的狀態:WAITINGCANCELLEDCOND
  • 三種子類:
  • ExclusiveNode:排他鎖
  • SharedNode:共享鎖
  • ConditionNode:條件隊列節點

2.2 隊列管理

  • head:隊列頭(虛擬節點,不實際佔用資源)。
  • tail:隊列尾(新線程入隊)。
  • 當線程競爭失敗時,會 封裝成 Node 入隊,排隊等待喚醒。

隊列特點:

  • FIFO 隊列,保證公平性(非公平鎖除外)。
  • 通過 prev/next 指針串聯。
  • 掛起線程不消耗 CPU。

3. acquire / release 模板方法

3.1 acquire(獲取鎖)

流程:

  1. 嘗試獲取鎖:調用子類 tryAcquiretryAcquireShared
  2. 如果失敗:
  • 封裝當前線程為 Node。
  • 入隊到 CLH 隊列。
  • 自旋幾次。
  • 自旋失敗 → park() 掛起。
  1. 被喚醒後:
  • 清理狀態。
  • 再次嘗試獲取鎖。
  1. 獲取鎖成功 → 節點成為 head。

注意:自旋 + park 是 AQS 的 CPU 友好策略:短時間可自旋,長時間掛起,不浪費資源。

當線程嘗試獲取鎖失敗時,AQS 並不是立即把線程掛起(park),而是先自旋幾次,這個策略叫 自旋等待(spin-wait)。原因有幾個:

  1. 短時間鎖衝突,直接自旋比掛起效率高
  • 如果鎖很快就會被釋放,比如持有鎖的線程只執行了幾行代碼,線程 park 再 unpark 的開銷比直接忙等(自旋)要高。
  • 自旋幾次能快速拿到鎖,減少線程切換開銷。
  1. 減少上下文切換
  • park/unpark 會涉及操作系統調度,切換線程狀態,比較昂貴。
  • 自旋在 CPU 層面循環檢查,不切換線程,短時間衝突非常高效。
  1. 避免“虛假喚醒”開銷
  • 自旋可以讓後續競爭的線程更快地接替鎖,而不是頻繁 park/unpark。

3.2 release(釋放鎖)

流程:

  1. 調用子類 tryRelease 修改 state
  2. 調用 signalNext(head)
  • 喚醒 head 後繼節點。
  • 通過 LockSupport.unpark(node.waiter) 恢復線程運行。
  1. 等待隊列中的線程重新競爭鎖。

4. 條件隊列 ConditionObject

  • AQS 提供了 ConditionObject,是 唯一實現了 Condition 接口的類
  • 條件隊列用於 await() / signal() 操作。

await() 流程:

  1. 封裝線程為 ConditionNode,放入條件隊列。
  2. 釋放鎖enableWait 保存 state,釋放資源)。
  3. 阻塞線程:park。
  4. signal() 喚醒後:
  • 從條件隊列移回 AQS 隊列。
  • 重新競爭鎖(acquire)。

ConditionObject 與 CLH 隊列協作,實現了 線程等待通知模式

需要注意,進入條件隊列的線程必須是持有鎖的,換句話説也就是進入await()的線程必須尺有所(例如ReentrantLock),await() 的設計思想:線程等待某個條件,在等待時必須釋放鎖,讓其他線程可以修改條件狀態。


signal() 流程:

  1. 從條件隊列取出節點。
  2. 移入 AQS CLH 隊列。
  3. 喚醒該節點。

5. 自旋 + park 機制

  • 自旋 (Thread.onSpinWait):
  • 短時間競爭鎖,CPU 快速輪詢。
  • park / unpark (LockSupport.park/unpark):
  • 長時間等待,釋放 CPU。
  • 被喚醒後,重新進入競爭。

這也是為什麼 acquire 中會有:失敗 → 入隊 → 自旋幾次 → park


6. Unsafe 與 AQS

UnsafeJDK 內部的低級工具類,提供:

  • 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

判斷鎖是否被當前線程持有

流程

  1. 線程調用 lock() → 調用 AQS acquire。
  2. acquire 調用 tryAcquire,獲取鎖成功返回;失敗 → 入隊 → 自旋 → park。
  3. unlock 調用 release,state 減 1,state==0 → signal 下一個節點。

Semaphore(信號量,共享鎖)

  • state:可用許可數量
  • 共享模式(Shared)

方法

實現邏輯

説明

tryAcquireShared(int acquires)

state > 0 → state -= acquires → 返回正數

獲取許可成功

tryReleaseShared(int releases)

state += releases → signalNext

釋放許可,並喚醒條件隊列線程

流程

  1. acquire() 調用 tryAcquireShared,如果失敗 → 入隊 → park 等待。
  2. 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;
}

解釋:

  1. 鎖空閒時
  • 嘗試通過 CAS 搶佔 state
  • 搶到鎖後,通過 setExclusiveOwnerThread(current) 記錄當前線程
  1. 鎖已被佔用時
  • 判斷 current == getExclusiveOwnerThread()
  • 如果是當前線程,就允許重入(state++)

所以判斷線程是否持有鎖,完全依賴 AOS 的 exclusiveOwnerThread,而不是隊列 head/tail。


AQS + AOS 關係總結


作用

AbstractOwnableSynchronizer

保存獨佔鎖持有者線程 (exclusiveOwnerThread)

AbstractQueuedSynchronizer

維護 state + CLH 隊列 + 提供 acquire/release 模板

ReentrantLock.Sync

實現 tryAcquire/tryRelease、可重入邏輯,操作 state + exclusiveOwnerThread

9.共享模式怎麼維護線程歸屬?

上文也説了AOS 只記錄獨佔模式下的持有線程,那共享模式例如CountDownLatchSemaphore這種怎麼判斷線程歸屬呢?

答案就是不用記錄線程歸屬,也就是説沒有方法能之間判斷改線程共享模式下是否持有鎖(或許説沒有鎖)

   其實這種模式下

  • 資源可以被多個線程共享訪問(共享計數)
  • 不關心“誰”佔用資源,只關心 state 是否允許訪問 實現:
  • state 記錄資源狀態(如 CountDownLatch 的計數器)
  • 線程調用 acquireShared() → 嘗試獲取 state
  • state <= 0 → 阻塞等待;state > 0 → 獲取成功

        注意

  • 共享模式下沒有 exclusiveOwnerThread 的意義
  • 沒有 API 可以問“當前線程持有鎖嗎”,因為本身沒有鎖歸屬概念