AQS原始碼閱讀

局外人~~發表於2022-04-22

簡介

AQS 全程為 AbstractQueuedSynchronizer , 在 java.util.concurrent.locks包下的一個抽象類。

類的具體作用以及設計在開始類描述資訊裡面就有很好的表達

Provides a framework for implementing blocking locks and related
synchronizers (semaphores, events, etc) that rely on
first-in-first-out (FIFO) wait queues.  This class is designed to
be a useful basis for most kinds of synchronizers that rely on a
single atomic {@code int} value to represent state. Subclasses
must define the protected methods that change this state, and which
define what that state means in terms of this object being acquired
or released.  Given these, the other methods in this class carry
out all queuing and blocking mechanics. Subclasses can maintain
other state fields, but only the atomically updated {@code int}
value manipulated using methods {@link #getState}, {@link
#setState} and {@link #compareAndSetState} is tracked with respect
to synchronization.	
具體翻譯為:
提供了實現阻塞鎖和相關的框架,依賴於的同步器(訊號量、事件等)。
先進先出 (FIFO) 等待佇列。 這個類被設計為對於大多數依賴於單個原子 {@code int} 值來表示狀態。 
子類必須定義更改此狀態的受保護方法,以及根據正在獲取的物件定義該狀態的含義或釋放。 鑑於這些,此類中的其他方法帶有排除所有排隊和阻塞機制。 子類可以維護其他狀態欄位,但只有原子更新的 {@code int}使用方法 {@link #getState}、{@link 操作的值#setState} 和 {@link #compareAndSetState} 被地跟蹤、同步。

簡單描述為:通過原子性的int值來標記同步狀態,實現阻塞鎖機制,基於FIFO佇列來實現排隊機制

AQS實現了兩種模式,獨佔模式和共享模式,實現共享鎖和排他鎖

以獨佔鎖為例,FIFO佇列頭節點獲取到鎖之後,其他節點會進入等待狀態,釋放鎖之後,頭節點的下一個節點會嘗試獲取鎖,但是不一定成功(公平、非公平)。

LockSupport工具類

LockSupport 官方是這樣描述的 Basic thread blocking primitives for creating locks and other , 翻譯為 “基本執行緒阻塞原語,用於建立鎖和其他”,可以理解為使用計算機原語來建立鎖,底層實現為unsafe類,主要方法為pack(執行緒阻塞)與unpack(喚醒執行緒),原始碼如下:

    // LockSupport設定阻塞分為兩組,
	// 一組是不設定blocker的方法,另一組是設定blocker方法
	// JDK推薦為Thread設定blocker方便除錯
	public static void park(Object blocker) {
        // 獲取當前執行緒
        Thread t = Thread.currentThread();
        // blocker位執行緒記憶體當前偏移量,設定執行緒記憶體偏移量,
        setBlocker(t, blocker);
        // Unsafe包下的方法可以直接操作記憶體,設定執行緒阻塞
        U.park(false, 0L);
        // 執行緒喚醒後 blockers設定為空
        setBlocker(t, null);
    }
    public static void park() {
        // 執行緒阻塞,單純喚醒,不會記錄偏移量
        U.park(false, 0L);
    }
	// 與park基本相同,新增阻塞事件,自動喚醒
	public static void parkNanos(Object blocker, long nanos);
	public static void parkUntil(Object blocker, long deadline);  
    // unpark方法為unsafe.unpark方法,為C++實現,基於記憶體進行操作
	public static void unpark(Thread thread) {
        if (thread != null)
            U.unpark(thread);
    }

AQS內部結構

AQS類圖

AQS的實現類為NofairSync(非公平)、FairSync(公平)兩種。

內部類Node

    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;
      // 節點在等待佇列中,節點執行緒等待在Condition上,當其他執行緒對Condition呼叫了signal()後,該節點將會從等待佇列中轉移到同步佇列中,加入到同步狀態的獲取中
      static final int CONDITION = -2;
      // 表示下一次共享式同步狀態獲取將會無條件地傳播下去
      static final int PROPAGATE = -3;
      // 等待狀態
      volatile int waitStatus;
      // 前驅結點
      volatile Node prev;
      // 後繼節點
      volatile Node next;
      // Node封裝當前執行緒
      volatile Thread thread;
      // 指向ConditionObject的下一個節點
      Node nextWaiter; 
    }

節點狀態

  1. CANCELLED (1):當前執行緒因為超時或者中斷被取消。這是一個終結態,也就是狀態到此為止
  2. SIGNAL (-1):當前執行緒的後繼執行緒被阻塞或者即將被阻塞,當前執行緒釋放鎖或者取消後需要喚醒後繼執行緒。這個狀態一般都是後繼執行緒來設定前驅節點的
  3. CONDITION (-2):當前執行緒在condition佇列中
  4. PROPAGATE (-3):用於將喚醒後繼執行緒傳遞下去,這個狀態的引入是為了完善和增強共享鎖的喚醒機制。在一個節點成為頭節點之前,是不會躍遷為此狀態的

主要屬性

/**
 * 等待佇列的頭節點, 賴載入
 * 除了初始化之外, 只能通過 setHead 方法來改變其值
 * 如果 head 不為 null, waitStatus 值就一定不會是 CANCELLED
 */
private transient volatile Node head;
 
/**
 * 等待佇列的尾結點, 懶載入
 * 只能通過 enq 方法新增新節點時才會去改變尾結點
 */
private transient volatile Node tail;
 
/**
 * 同步器的狀態
 * 以 ReentrantLock 為例, 0 表示可以獲取到鎖, 其他的正整數表示無法獲取到鎖
 */
private volatile int state;

AQS屬性結構設計如下

具體方法

開放實現介面API,用於場景自定義:

方法 作用
boolean tryAcquire(int arg) 試獲取獨佔鎖
boolean tryRelease(int arg) 試釋放獨佔鎖
int tryAcquireShared(int arg) 試獲取共享鎖
boolean tryReleaseShared(int arg) 試釋放共享鎖
boolean isHeldExclusively() 當前執行緒是否獲得了獨佔鎖

AQS本身將同步狀態的管理用模板方法模式都封裝好了,以下列舉了AQS中的一些模板方法

方法 描述
void acquire(int arg) 獲取獨佔鎖。會呼叫tryAcquire方法,如果未獲取成功,則會進入同步佇列等待
void acquireInterruptibly(int arg) 響應中斷版本的acquire
boolean tryAcquireNanos(int arg,long nanos) 響應中斷+帶超時版本的acquire
void acquireShared(int arg) 獲取共享鎖。會呼叫tryAcquireShared方法
void acquireSharedInterruptibly(int arg) 響應中斷版本的acquireShared
boolean tryAcquireSharedNanos(int arg,long nanos) 響應中斷+帶超時版本的acquireShared
boolean release(int arg) 釋放獨佔鎖
boolean releaseShared(int arg) 釋放共享鎖
Collection getQueuedThreads() 獲取同步佇列上的執行緒集合

原始碼層面上會對acquire、release、acquireShared 、releaseShared 進行詳解

獨佔鎖

// 獲取獨佔鎖  	
public final void acquire(int arg) {
        if (!tryAcquire(arg) && // 試獲取獨佔鎖,獲取同步狀態 
            // addWaiter,將執行緒封裝成Node節點,新增到同步佇列的尾部,acquireQueued,自旋等待,如果獲取狀態失敗,掛起執行緒
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) 
            selfInterrupt(); // 執行緒中斷
}

private Node addWaiter(Node mode) {
    //建立Node節點
    Node node = new Node(mode);

    for (;;) {
        // 當前隊尾進行標記
        Node oldTail = tail;
        if (oldTail != null) {
            node.setPrevRelaxed(oldTail);
            // CAS操作放到隊尾
            if (compareAndSetTail(oldTail, node)) {
                oldTail.next = node;
                return node;
            }
        } else {
            initializeSyncQueue();
        }
    }
}
// 將該執行緒加入等待佇列的尾部,並標記為獨佔模式
final boolean acquireQueued(final Node node, int arg) {
    boolean interrupted = false;
    try {
        // 自旋,不斷嘗試當前執行緒獲取鎖,知道成功或者執行緒被打斷
        for (;;) {
            // 獲取前驅節點
            final Node p = node.predecessor();
            // 嘗試獲取鎖
            if (p == head && tryAcquire(arg)) {
                // 成功之後將節點設定為新的頭部節點
                setHead(node);
                p.next = null; // help GC
                return interrupted;
            }
            // 獲取鎖失敗
            // 判斷執行緒是否需要中斷, 判斷標準是前節點狀態
            if (shouldParkAfterFailedAcquire(p, node))
                //掛起當前執行緒
                interrupted |= parkAndCheckInterrupt();
        }
    } catch (Throwable t) {
        cancelAcquire(node);
        if (interrupted)
            selfInterrupt();
        throw t;
    }
}
// 如果執行緒獲取同步狀態失敗就要檢查它的節點status,要保證prev = node.prev
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    // 前驅節點狀態
    int ws = pred.waitStatus;
    // singal 表示前驅節點釋放後會喚起當前執行緒,會繼續等待,那麼執行緒可以中斷等待
    if (ws == Node.SIGNAL)
        return true;
    // 前驅節點不是Head節點,並且狀態為CANCELLED,那麼就需要剔除前置節點,向前遍歷,直到找到合適的前置
    if (ws > 0) {
        do {
            node.prev = pred = pred.prev;
        } while (pred.waitStatus > 0);
        pred.next = node;
    } else {
        // 這種情況表示前驅節點的 ws = 0 或者 ws = PROPAGATE,那麼設定前驅節點為singal,之後重新迴圈獲取鎖
        pred.compareAndSetWaitStatus(ws, Node.SIGNAL);
    }
    return false;
}

acquire方法為獲取獨佔鎖

  1. tryAcquire() 嘗試直接去獲取資源,如果成功則直接返回true,AQS中空方法,由實現類來實現,具體分析會在ReentrantLock中進行詳細程式碼分析。
  2. addWaiter() 將該執行緒加入等待佇列的尾部,並標記為獨佔模式
  3. acquireQueued()使執行緒阻塞在等待佇列中獲取資源,一直獲取到資源後才返回。如果在整個等待過程中被中斷過,則返回true,否則返回false。如果執行緒在等待過程中被中斷過,它是不響應的。只是獲取資源後才再進行自我中斷selfInterrupt(),將中斷補上
// 獨佔鎖釋放節點
public final boolean release(int arg) {
    // tryRelease由實現類具體實現
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)
            // 喚醒下一個節點
            unparkSuccessor(h);
        return true;
    }
    return false;
}

release方法通過tryRelease()實現了擴充套件性,來進行鎖的重入設計,unparkSuccessor則自身實現,完成了下一個Node節點的喚醒

共享鎖

public final void acquireShared(int arg) {
    // tryAcquireShared為抽象方法,由子類實現
    if (tryAcquireShared(arg) < 0)
        doAcquireShared(arg);
}	

private void doAcquireShared(int arg) {
    // 不斷嘗試從佇列中獲取共享鎖,當成功獲取到鎖或者執行緒被打斷時會成功退出迴圈,競爭鎖失敗的執行緒會被 park 直到被喚醒,喚醒之後會再次進入迴圈嘗試去獲取鎖,不斷的重複整個過程
    final Node node = addWaiter(Node.SHARED);
    boolean interrupted = false;
    try {
        for (;;) {
            final Node p = node.predecessor();
            if (p == head) {
                int r = tryAcquireShared(arg);
                if (r >= 0) {
                    setHeadAndPropagate(node, r);
                    p.next = null; // help GC
                    return;
                }
            }
            // 上面由詳細解讀
            if (shouldParkAfterFailedAcquire(p, node))
                interrupted |= parkAndCheckInterrupt();
        }
    } catch (Throwable t) {
        cancelAcquire(node);
        throw t;
    } finally {
        if (interrupted)
            selfInterrupt();
    }
}

// 釋放共享鎖
public final boolean releaseShared(int arg) {
    // 子類實現
    if (tryReleaseShared(arg)) {
        
        doReleaseShared();
        return true;
    }
    return false;
}
//當前節點呼叫
private void doReleaseShared() {
    for (;;) {
        Node h = head;
        if (h != null && h != tail) {
            int ws = h.waitStatus;
            // 如果頭節點狀態為SIGNAL, 表示可以去釋放鎖
            if (ws == Node.SIGNAL) {
                // 通過 cas 將 waitStatus 設為 0
                if (!h.compareAndSetWaitStatus(Node.SIGNAL, 0))
                    continue;            // loop to recheck cases
                // 執行緒喚醒
                unparkSuccessor(h);
            }
            else if (ws == 0 &&
                     !h.compareAndSetWaitStatus(0, Node.PROPAGATE))
                continue;                // loop on failed CAS
        }
        if (h == head)                   // loop if head changed
            break;
    }
}

關於獨佔鎖和共享鎖的異同

相同

1)獲取鎖前都會判斷是否有許可權,只有滿足條件才可能獲取到鎖

2)未獲取到鎖的執行緒會建立新節點放入佇列尾部

不同

1)獨佔鎖只會釋放頭部後節點的執行緒,而共享鎖會依次釋放所有執行緒

2)獨佔鎖存在非公平鎖的情況,新的執行緒可能搶佔佇列中執行緒的鎖,共享鎖則不存在這種情況

擴充套件

AQS使用了模板方法的設計模式,以固定的方法進行呼叫,子類對其實現,方便擴充套件,後續會對具體實現類繼續讀原始碼。

相關文章