AbstractQueuedSynchronizer框架淺析

AbstractQueuedSynchronizer框架淺析

1.概述

AbstractQueuedSynchronizer(AQS)抽象類提供一個實現阻塞鎖和依賴于FIFO等待隊列同步器的框架。
AQS被設計用來作為眾多同步器的基類,例如ReentrantLock、Semaphore、CountDownLatch、FutureTask以及ReentrantReadWriteLock。AQS依賴于一個整數值,用來代表狀態。AQS的子類可以通過覆寫protect方法來改變狀態,并且定義狀態代表具體什么含義。不同的同步器對狀態的含義解釋不同:

  • 在ReentrantLock中,AQS的同步狀態用于保存鎖獲取操作的次數;
  • 在FutureTask中,AQS同步狀態被用來保存任務的狀態
  • 在Semaphore和CountDownLatch中,AQS的同步狀態用于保存當前可用許可的數量;

針對AQS中的狀態,只可以通過AQS的getState()、setState()和compareAndSetState()方法來改變狀態。

同步器類應該使用私有的AQS子類來實現功能,而不應該直接繼承AQS類。AQS類沒有實現任何同步接口,它只是定義了一些可以被具體同步器和鎖調用的方法,例如acquireInterruptibly方法。AQS子類需要自己實現以下方法:

  • tryAcquire()
  • tryRelease()
  • tryAcquireShared()
  • tryReleaseShared()
  • isHeldExclusively()

這些方法默認會拋出UnsupportedOperationException異常。在這些方法實現中,可以使用getState()、setState()和compareAndSetState()來獲取或修改AQS的狀態。這些方法的實現必須是線程安全的,并且應該實現簡單且沒有阻塞。只有這些方法可以被子類覆寫,其他的AQS的公開方法都是final方法。

AQS同時支持獨占操作模式(例如ReentrantLock)和非獨占操作模式(例如Semaphore和CountDownLatch)。當以獨占模式獲取鎖時,只有一個線程能訪問成功,其他線程都訪問失??;而以非獨占模式獲取鎖時,多個線程可以同時訪問成功。不同操作模式的線程都在同一個FIFO隊列中等待。通常,AQS的子類只支持一種操作模式(獨占或非獨占),但也有同時支持兩種操作模式的同步器,例如ReadWriteLock的子類,它的讀取鎖是非獨占操作模式,而寫入鎖是獨占操作模式。

由于在將線程放入FIFO等待隊列之前,需要嘗試一次acquire,因此有可能新的acquire線程可以獲取成功,盡管等待隊列中還有其他線程阻塞等待,這是一種非公平策略。然而,可以在tryAcquire()或者tryAcquireShared()方法中禁止線程搶占,具體是通過hasQueuedPredecessors()方法判斷等待隊列中是否有線程在阻塞等待,如果有線程阻塞等待,則讓tryAcquire()或者tryAcquireShared()方法返回false,這樣的話,acquire線程會被放入等待隊列的尾部,然后喚醒阻塞等待的線程,這是一種公平的策略。

AQS類為同步器的狀態、參數的獲取和釋放,以及內部FIFO等待隊列,提供了一個高效的和可擴展的基礎。當這些不能滿足你的要求時,你可以自定義java.util.concurrent.atomic原子類,自定義java.util.Queue類,以及LockSupport類來提供阻塞支持。

AQS框架的一個類圖如下所示:

AQS框架類圖

2.源碼分析

2.1 AbstractQueuedSynchronizer類的繼承關系

AQS類繼承了AbstractOwnableSynchronizer類,實現了Serializable接口。AbstractOwnableSynchronizer類主要是用來保存同步器被哪個線程獨占使用。

public abstract class AbstractQueuedSynchronizer
extends AbstractOwnableSynchronizer
implements java.io.Serializable {
    ......
}

public abstract class AbstractOwnableSynchronizer
implements java.io.Serializable {

    protected AbstractOwnableSynchronizer() { }
    
    //The current owner of exclusive mode synchronization.
    private transient Thread exclusiveOwnerThread;
    
    protected final void setExclusiveOwnerThread(Thread thread) {
        exclusiveOwnerThread = thread;
    }

    protected final Thread getExclusiveOwnerThread() {
        return exclusiveOwnerThread;
    }
}

2.2 Node內部類

在AQS類中,定義了一個FIFO等待隊列節點的內部類Node。

static final class Node {
    /** Marker to indicate a node is waiting in shared mode */
    static final Node SHARED = new Node();
    /** Marker to indicate a node is waiting in exclusive mode */
    static final Node EXCLUSIVE = null;

    /** waitStatus value to indicate thread has cancelled */
    static final int CANCELLED =  1;
    /** waitStatus value to indicate successor's thread needs unparking */
    static final int SIGNAL    = -1;
    /** waitStatus value to indicate thread is waiting on condition */
    static final int CONDITION = -2;

    /**
     * waitStatus value to indicate the next acquireShared should
     * unconditionally propagate
     */
    static final int PROPAGATE = -3;

    /*
    * 非負值意味著該節點不需要signal。
    * 該值默認初始化為0,表示普通的同步節點,主要是通過CAS方法來修改該變量值。
    */
    volatile int waitStatus;

    /*
    * 指向前繼節點,當前節點需要依賴于前繼節點來檢查waitStatus。
    * 在入隊列的時候賦值,在出隊列的時候為null。
    */
    volatile Node prev;
    
    /*
    * 指向后繼節點,當前節點依賴后繼節點來喚醒釋放
    * 在入隊列的時候賦值,在出隊列的時候為null。
    */
    volatile Node next;

    /*
    * 將該節點入隊列的線程,在構造節點的時候初始化,使用完之后變為null
    */
    volatile Thread thread;

    /*
    * 指向下一個在條件等待,或者是特定的SHARED值的節點。
    * 條件隊列只有在獲取獨占鎖時才可以被訪問,我們需要一個單鏈表隊列來保存節點,當他們在條件上等待時。
    */
    Node nextWaiter;

     /**
     * Returns true if node is waiting in shared mode.
     */
    final boolean isShared() {
        return nextWaiter == SHARED;
    }

    final Node predecessor() throws NullPointerException {
        Node p = prev;
        if (p == null)
            throw new NullPointerException();
        else
            return p;
    }

    Node() {    // Used to establish initial head or SHARED marker
    }

    Node(Thread thread, Node mode) {     // Used by addWaiter
        this.nextWaiter = mode;
        this.thread = thread;
    }

    Node(Thread thread, int waitStatus) { // Used by Condition
        this.waitStatus = waitStatus;
        this.thread = thread;
    }
}

等待隊列是CLH鎖隊列的變種,CLH鎖通常被用來實現自旋鎖。在節點中的waitStatus域保存了線程是否需要被阻塞的信息。當一個節點的前繼節點被釋放了,節點將會收到通知。在等待隊列中的每一個節點,都充當著特定通知形式的監控器,并且持有一個等待線程。節點中的waitStatus域并不控制線程是否能獲取到鎖。如果一個線程是隊列中的第一個線程,并不能保證它能競爭成功獲取到鎖,而只是給予了它參與競爭的權利。

為了將一個Node節點放入到LCH鎖隊列中,只需要將該Node節點拼接到隊列尾部就行;如果為了出隊列,則只需設置隊列的頭指針位置head。

       +------+  prev +-----+       +-----+
  head |      | <---- |     | <---- |     |  tail
       +------+       +-----+       +-----+

插入節點到LCH隊列中,只需一個在tail域上的原子操作。相似的,出隊列僅僅需要更新head域,但是出隊列還需要做一些額外的工作,來決定新的head節點的后繼節點是哪個,其中需要考慮的因素有線程是否被取消了,操作是否超時了以及線程是否被中斷了。

在Node節點中的prev域主要用來處理節點被取消的情況。如果一個Node節點被取消了,那邊它的后繼節點需要重新連接到一個未被取消的前繼節點。

在Node節點中的next域主要被用來實現阻塞機制。在每個節點中都保存有線程ID,因此當需要喚醒下一個節點時,只需要通過遍歷next域,找到后繼節點,通過節點獲取到線程ID,從而知道該喚醒哪個線程。

等待狀態只能取以下幾個值:

  • SIGNAL:該節點的后繼節點當前是阻塞的,因此當前節點在釋放和取消之后,必須喚醒它的后繼節點。為了避免競爭,acquire方法必須首先進入SIGNAL等著狀態,然后再嘗試原子獲取,這個獲取過程可能會失敗或者阻塞。
  • CANCELLED:該節點由于超時或者中斷了被取消了。節點進入了CANCELLED狀態之后,就不會再發生狀態的變化了。特別地,處于CANCELLED狀態節點的線程不會再被阻塞了。
  • CONDITION:該節點當前處于一個條件隊列中。經過轉移之后,該節點將會被作為同步隊列的節點使用,此時節點的狀態會被設置為0。
  • PROPAGATE:releaseShared方法應該傳遞給其他Node節點。在doReleaseShared方法中,確保傳遞會繼續。
  • 0:非以上任何狀態;

等待狀態值使用數值來簡化使用,非負值意味著節點不需要被通知喚醒,因此大多數代碼只需檢查數值的正負就可以知道是否需要喚醒了。

2.3 AQS類的重要成員變量

/*
* 等待隊列的頭節點,延遲初始化
* 除了初始化可以修改head值,還可以通過setHead方法設置
* 只要head節點存在,那么該節點的waitStatus狀態將不會是CANCELLED
*/
private transient volatile Node head;

/*
* 等待隊列的尾節點,延遲初始化
* 只能通過enq方法來添加一個新的等待節點到隊列中,從而來修改tail值
*/
private transient volatile Node tail;

/*
* 同步狀態
*/
private volatile int state;

可以看到,在AQS類中,有三個比較重要的成員變量,其中兩個是表示等待隊列的頭指針和尾指針。另外一個表示同步的狀態。

與state相關的方法有三個:

/*
* 獲取當前同步狀態
*/
protected final int getState() {
    return state;
}

/*
* 設置新的同步狀態
*/
protected final void setState(int newState) {
    state = newState;
}

/*
* 采用CAS方法來更新同步狀態
* expect 期望的值
* update 更新為新的值
*/
protected final boolean compareAndSetState(int expect, int update) {
    // See below for intrinsics setup to support this
    return unsafe.compareAndSwapInt(this, stateOffset, expect, update);//采用了sun.misc.Unsafe類來實現CAS操作
}

在AQS中使用到了sun.misc.Unsafe類來實現CAS操作,Unsafe類的compareAndSwapInt()和compareAndSwapLong()等方法包裝了CAS操作,虛擬機在內部對這些方法做了特殊處理,即時編譯出來的結果就是一條平臺相關的處理器CAS指令。

與head和tail相關的方法有兩個:

/*
* 將隊列的頭結點設置為node結點
* 該方法只被acquire系列方法調用
*/
private void setHead(Node node) {
    head = node;//讓head指針指向node結點
    node.thread = null;
    node.prev = null;
}

/*
* 插入node節點到隊列中,必要時初始化
* 返回node節點的前繼節點,即原來的尾節點
*/
private Node enq(final Node node) {
    for (;;) {
        Node t = tail;
        // 尾節點為空,則先進行初始化操作,創建一個新的node節點
        if (t == null) { // Must initialize
            if (compareAndSetHead(new Node()))//head節點被成功初始化后,將tail節點指向head節點
                tail = head;
        } else {
            node.prev = t;
            if (compareAndSetTail(t, node)) {//將tail節點更新為node節點 
                t.next = node;
                return t;//返回node的前繼節點
            }
        }
    }
}

可以看到,head和tail節點的初始化操作是在setHead()和enq()方法中進行的,同時更新操作也是在這兩個方法中進行的。

2.4 AQS類暴露給子類實現的方法

/*
* 嘗試以獨占模式獲取
* 該方法應該查詢對象的狀態是否允許以獨占模式獲取,如果允許,則獲取。
* 該方法總是被執行acquire方法的線程執行,如果該方法失敗了,則acquire方法將該線程放入隊列中,直到有其他線程調用了release方法來發送通知信號。
* 如果獲取成功了,則返回true。
*/
protected boolean tryAcquire(int arg) {
    throw new UnsupportedOperationException();
}

/*
* 嘗試修改狀態來反映以獨占模式釋放
* 該方法總是被執行釋放的線程觸發
* 如果該對象處于完全釋放的狀態則返回true
*/
protected boolean tryRelease(int arg) {
    throw new UnsupportedOperationException();
}

/*
* 嘗試以非獨占模式來獲取
* 該方法應該查詢是否對象允許以非獨占模式來獲取,如果允許,則獲取。
* 該方法總是被執行acquire方法的線程執行。如果該方法失敗了,則acquire方法將該線程放入隊列中,直到有其他線程調用了release方法來發送通知信號
* 返回值為負值,表示失??;
* 返回0,表示以非獨占模式獲取成功,但后續的以非獨占模式獲取將失敗
* 返回正值,表示以非獨占模式獲取成功,后續的以非獨占模式獲取也會成功
*/
protected int tryAcquireShared(int arg) {
    throw new UnsupportedOperationException();
}

/*
* 嘗試修改狀態來反映以非獨占模式釋放
* 該方法總是被執行釋放的線程觸發
* 返回true,表示以非獨占模式釋放成功
*/
protected boolean tryReleaseShared(int arg) {
    throw new UnsupportedOperationException();
}

/*
* 如果同步器是被當前線程以獨占模式訪問,則返回true。
* 該方法在每次調用非等待ConditionObject方法時被觸發。
*/
protected boolean isHeldExclusively() {
    throw new UnsupportedOperationException();
}

2.5 AQS類定義的acquire系列方法

2.5.1 acquire()方法

/*
* 以獨占模式獲取,屏蔽中斷
* 至少觸發一次tryAcquire()方法,如果獲取成功,直接返回
* 如果獲取失敗,則線程入隊列,然后通過tryAcquire()不斷嘗試,直至成功
* 該方法可以被用來實現Lock.lock()方法
*/
public final void acquire(int arg) {
    if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

tryAcquire()方法是空實現,需要子類覆寫該方法,實現具體的獲取操作。addWaiter()方法主要是為當前線程創建一個新的Node節點,并把該Node節點以指定的模式存放入隊列中。

/*
* 為當前線程創建一個Node節點,并且設置為指定mode,最后將該node節點放入等待隊列中
*  @param mode mode有兩種取值:Node.EXCLUSIVE和Node.SHARED,分別代表以獨占模式和非獨占模式
*  @return 返回新的Node節點
*/
private Node addWaiter(Node mode) {
    Node node = new Node(Thread.currentThread(), mode);//給當前線程創建一個新的Node節點,節點模式為mode
    // Try the fast path of enq; backup to full enq on failure
    Node pred = tail;
    if (pred != null) {
        node.prev = pred;//將node節點的前繼指針指向尾節點
        if (compareAndSetTail(pred, node)) {//更新tail指針指向node節點
            pred.next = node;//將原來tail節點的后繼指針指向node節點
            return node;//這樣node節點成功鏈接到tail節點后面,并更新tail節點指向node節點了
        }
    }
    // 如果前面將node節點入隊列失敗,則再通過enq()方法入隊列,其實現思想和上面的過程一致
    enq(node);
    return node;
}

addWaiter()方法首先根據mode模式給當前線程創建一個node節點,然后將該node節點放入隊列的尾部。acquireQueued()方法以獨占不可中斷方式獲取。

/*
* 為隊列中的線程,以獨占不可中斷模式獲取
* 該方法被條件等待和acquire方法使用  
* @param node 節點
* @param arg 獲取的參數
* @return 在等待的過程中被中斷了,則返回true
*/
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)) {//如果前繼節點等于head節點,并且嘗試獲取成功
                setHead(node);//更新head節點為node節點
                p.next = null; // help GC
                failed = false;
                return interrupted;//返回是否中斷了
            }
            // 在獲取失敗之后,判斷是否需要掛起該線程,如果需要掛起,則通過LockSupport.lock()方法掛起該線程,等線程被喚醒后判斷是否被中斷過
            if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)//發生了異常,則取消獲取操作
            cancelAcquire(node);//取消獲取操作
    }
}

在acquireQueued()方法中,從尾節點開始循環前向遍歷,如果當前節點的前繼節點是頭節點,并且tryAcquire()方法返回true了,則更新頭結點,并返回。如果在向前遍歷的過程中,遇到了節點獲取失敗需要掛起時,則會通過LockSupport的park()將當前線程掛起。

/*
* 檢查和更新獲取失敗節點的狀態
* 如果線程應該被阻塞,則返回true
* 該方法需要滿足pred == node.prev
* @param pred node節點的前繼節點,保存狀態
* @param node node節點
* @return 如果線程應該被阻塞,則返回true
*/
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    int ws = pred.waitStatus;//獲取前繼節點的等待狀態
    if (ws == Node.SIGNAL)// SIGNAL狀態
        /*
         * This node has already set status asking a release
         * to signal it, so it can safely park.
         */
        return true;
    if (ws > 0) {//CANCELLED狀態
        /*
         * Predecessor was cancelled. Skip over predecessors and
         * indicate retry.
         */
        do {
            node.prev = pred = pred.prev;//向前遍歷搜索,找出前繼節點
        } while (pred.waitStatus > 0);
        pred.next = node;
    } else {// waitStatus為0或者為PROPAGATE
        /*
         * waitStatus must be 0 or PROPAGATE.  Indicate that we
         * need a signal, but don't park yet.  Caller will need to
         * retry to make sure it cannot acquire before parking.
         */
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);//設置waitStatus為SIGNAL
    }
    return false;
}

在shouldParkAfterFailedAcquire()方法中,根據pred節點的等待狀態做出相應的處理:

  • SIGNAL狀態:說明該節點已經更新了狀態,請求釋放,因此可以返回true
  • CANCELLED狀態:說明節點已經被取消了,忽略前繼節點狀態為CANCELLED的節點,同時更新node和pred節點值
  • PROPAGATE狀態或0:說明節點需要更新狀態為SIGNAL。

可以看到,shouldParkAfterFailedAcquire()方法只有節點等待狀態為SIGNAL時,才會返回true。后續才會執行parkAndCheckInterrupt()方法。

/*
* 停止當前線程參與系統調度,即掛起當前線程,并返回當前線程是否被中斷了
*/
private final boolean parkAndCheckInterrupt() {
    LockSupport.park(this);//掛斷當前線程
    return Thread.interrupted();//返回線程是否被中斷了
}

LockSupport.park()方法:

/*
* 掛起線程
* 當發生了以下幾種情況,可以喚醒線程:
* 1.其他線程觸發了LockSupport.unlock()方法,喚醒線程
* 2.其他線程觸發了Thread.interrupt()方法,打斷當前線程
* 3.該調用無條件返回了
* 該方法不會記錄什么原因導致該方法返回了,因此方法調用者,需要自己重新檢查導致線程掛起的條件
* @param blocker 導致線程掛起的同步器
*/
public static void park(Object blocker) {
    Thread t = Thread.currentThread();
    setBlocker(t, blocker);
    UNSAFE.park(false, 0L);
    setBlocker(t, null);
}

從前面的一些方法調用可以看到,acquire方法主要分為兩種情況來處理:

  • 第一次通過tryAcquire()方法獲取成功了,則直接返回;
  • 當第一次tryAcquire()失敗時,接下來為當前線程創建一個以獨占操作模式的Node節點,并把Node節點放入等待隊列的尾部。然后在acquireQueued()方法中,從尾節點開始向前,循環從等待隊列中取出節點,判斷是否允許獲取操作。如果不允許獲取,即需要阻塞,則通過LockSupport.lock()方法掛起當前線程。當其他線程解除了當前線程的阻塞,或者是發生了中斷,則返回繼續下一個循環。最終只有遍歷到頭結點head,并且tryAcquire()方法返回true時,才會從acquireQueued()方法中退出,代表獲取完成了。

2.5.2 acquireInterruptibly()方法

/*
* 以獨占模式獲取,如果發生了中斷,則停止獲取
* 在獲取之前,首先檢查線程是否被中斷過,然后至少嘗試一次tryAcquire()方法
* 如果第一次tryAcquire()方法失敗,則將線程放入等待隊列中,然后循環調用tryAcquire()方法,直至返回成功或者線程被中斷了
* 該方法可以被用來實現Lock.lockInterruptibly()
*/
public final void acquireInterruptibly(int arg)
        throws InterruptedException {
    if (Thread.interrupted())//如果線程被中斷了,則直接返回異常
        throw new InterruptedException();
    if (!tryAcquire(arg))
        doAcquireInterruptibly(arg);
}

當tryAcquire()失敗時,則調用doAcquireInterruptibly()方法重復的獲取。

/*
* 以獨占可中斷模式獲取
*/
private void doAcquireInterruptibly(int arg)
    throws InterruptedException {
    final Node node = addWaiter(Node.EXCLUSIVE);//給當前線程,創建一個獨占模式的節點,并把節點放入到等待隊列尾部
    boolean failed = true;
    try {
        for (;;) {//從尾節點開始循環處理
            final Node p = node.predecessor();//當前節點的前繼節點
            if (p == head && tryAcquire(arg)) {
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return;
            }
            // 在獲取失敗之后,判斷是否需要掛起該線程,如果需要掛起,則通過LockSupport.lock()方法掛起該線程,等線程被喚醒后判斷是否被中斷過
            if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())
                throw new InterruptedException();// 如果線程被中斷過,則拋出異常
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

可以看到doAcquireInterruptibly()方法與acquireQueued()實現大致相似,唯一的不同之處是,doAcquireInterruptibly()檢測到線程被中斷之后,會拋出一個中斷異常。

2.5.3 acquireShared()方法

/*
* 以非獨占模式獲取,屏蔽中斷
* 至少會觸發調用一次tryAcquireShared()
* 如果調用tryAcquireShared()失敗了,則將該線程放入等待隊列中,并且會不斷的嘗試tryAcquireShared()方法,直到返回成功
*/
public final void acquireShared(int arg) {
    if (tryAcquireShared(arg) < 0)
        doAcquireShared(arg);
}

在tryAcquireShared()方法獲取失敗,會調用doAcquireShared()繼續重復的獲取。

/*
* 以非獨占不可中斷模式獲取
*/
private void doAcquireShared(int arg) {
    final Node node = addWaiter(Node.SHARED);//為當前線程創建一個非獨占模式的Node節點,并把給Node節點放入到等待隊列的尾部
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {//從尾節點開始循環,不斷向前遍歷節點
            final Node p = node.predecessor();
            if (p == head) {//遍歷到頭節點
                int r = tryAcquireShared(arg);
                if (r >= 0) {//獲取成功了,直接返回
                    setHeadAndPropagate(node, r);//更新頭結點和同步狀態
                    p.next = null; // help GC
                    if (interrupted)
                        selfInterrupt();
                    failed = false;
                    return;
                }
            }
            // 在獲取失敗之后,判斷是否需要掛起該線程,如果需要掛起,則通過LockSupport.lock()方法掛起該線程,等線程被喚醒后判斷是否被中斷過
            if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

doAcquireShared()方法與acquireQueued()方法實現類似,唯一不同之處是獲取成功之后,會調用setHeadAndPropagate()方法來更head節點以及同步狀態。

private void setHeadAndPropagate(Node node, int propagate) {
    Node h = head; // Record old head for check below
    setHead(node);
    /*
     * Try to signal next queued node if:
     *   Propagation was indicated by caller,
     *     or was recorded (as h.waitStatus either before
     *     or after setHead) by a previous operation
     *     (note: this uses sign-check of waitStatus because
     *      PROPAGATE status may transition to SIGNAL.)
     * and
     *   The next node is waiting in shared mode,
     *     or we don't know, because it appears null
     *
     * The conservatism in both of these checks may cause
     * unnecessary wake-ups, but only when there are multiple
     * racing acquires/releases, so most need signals now or soon
     * anyway.
     */
    if (propagate > 0 || h == null || h.waitStatus < 0 ||
        (h = head) == null || h.waitStatus < 0) {
        Node s = node.next;
        if (s == null || s.isShared())
            doReleaseShared();
    }
}

在以下幾種情況下,會對Node節點的后繼節點進行判斷是否需要釋放:

  • propagate大于0;
  • 之前的頭結點head為空;
  • 之前的頭結點head的waitStatus狀態小于0;
  • 當前的頭結點head為空;
  • 當前的頭結點head的waitStatus狀態小于0;

在滿足以上幾種情況后,如果后繼節點為空,或者后繼節點是非獨占模式的,則執行釋放操作。

/*
* 非獨占模式的釋放
* 通知后繼節點并且確保釋放的傳遞
*/
private void doReleaseShared() {
    /*
     * Ensure that a release propagates, even if there are other
     * in-progress acquires/releases.  This proceeds in the usual
     * way of trying to unparkSuccessor of head if it needs
     * signal. But if it does not, status is set to PROPAGATE to
     * ensure that upon release, propagation continues.
     * Additionally, we must loop in case a new node is added
     * while we are doing this. Also, unlike other uses of
     * unparkSuccessor, we need to know if CAS to reset status
     * fails, if so rechecking.
     */
    for (;;) {// 從頭結點開始循環
        Node h = head;
        // 如果頭結點不為空,并且頭結點不等于尾節點
        if (h != null && h != tail) {
            int ws = h.waitStatus;//等待狀態
            if (ws == Node.SIGNAL) {//等待狀態為SIGNAL
                if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))//1.將Node節點的狀態更新為0
                    continue;            // loop to recheck cases
                unparkSuccessor(h);//喚醒h節點的后繼節點
            }else if (ws == 0 && !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))//2.將Node節點的狀態更新為PROPAGATE
                continue;                // loop on failed CAS
        }
        if (h == head)                   // loop if head changed
            break;
    }
}

可以看到,在doReleaseShared()方法中,主要的工作有:

  • 先將頭節點從SIGNAL狀態更新為0,并且喚醒頭結點的后繼節點;

  • 將頭節點的狀態從0更新為PROPAGATE;

  • 如果頭結點在更新狀態的時候沒有發生改變,則退出循環;

    /*

    • 喚醒Node的后繼節點
      /
      private void unparkSuccessor(Node node) {
      /

      • If status is negative (i.e., possibly needing signal) try
      • to clear in anticipation of signalling. It is OK if this
      • fails or if status is changed by waiting thread.
        */
        int ws = node.waitStatus;
        if (ws < 0)
        compareAndSetWaitStatus(node, ws, 0);

      /*

      • Thread to unpark is held in successor, which is normally
      • just the next node. But if cancelled or apparently null,
      • traverse backwards from tail to find the actual
      • non-cancelled successor.
        */
        Node s = node.next;//Node的后繼節點
        if (s == null || s.waitStatus > 0) {// 后繼節點為空,或者后繼節點的等待狀態大于0,則嘗試從尾部節點開始到Node節點為止,尋找最靠近Node節點的等待狀態小于等于0的節點
        s = null;
        for (Node t = tail; t != null && t != node; t = t.prev)
        if (t.waitStatus <= 0)
        s = t;
        }
        // 存在后繼節點,其等待狀態小于等于0
        if (s != null)
        LockSupport.unpark(s.thread);//喚醒該節點關聯的線程
        }

在unparkSuccessor()方法中,主要是喚醒Node的后繼節點中等待狀態小于等于0的節點。

2.5.4 acquireSharedInterruptibly()方法

/*
* 以非獨占模式獲取,如果發生了中斷,則停止獲取
* 在獲取之前,首先檢查線程是否被中斷過,然后至少嘗試一次tryAcquireShared()方法
* 如果調用tryAcquireShared()失敗了,則將該線程放入等待隊列中,并且會不斷的嘗試tryAcquireShared()方法,直到返回成功
*/
public final void acquireSharedInterruptibly(int arg)
        throws InterruptedException {
    if (Thread.interrupted())//線程被中斷了
        throw new InterruptedException();
    if (tryAcquireShared(arg) < 0)
        doAcquireSharedInterruptibly(arg);
}

在tryAcquireShared()返回失敗后,會調用doAcquireSharedInterruptibly()方法重復嘗試獲取。

/*
* 以非獨占可中斷模式獲取
*/
private void doAcquireSharedInterruptibly(int arg)
    throws InterruptedException {
    final Node node = addWaiter(Node.SHARED);
    boolean failed = true;
    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
                    failed = false;
                    return;
                }
            }
            // 在獲取失敗之后,判斷是否需要掛起該線程,如果需要掛起,則通過LockSupport.lock()方法掛起該線程,等線程被喚醒后判斷是否被中斷過
            if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())
                throw new InterruptedException();//拋出中斷異常
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

可以看到doAcquireSharedInterruptibly()方法與doAcquireShared()方法實現類似,唯一的不同之處是,在線程等待的過程中,如果被中斷了,則會拋出中斷異常。

2.5.5 tryAcquireNanos()

/*
* 嘗試以獨占模式獲取,如果發生了中斷則停止,如果超時了,則返回失敗
* 在獲取之前,首先檢查線程是否被中斷過,然后至少嘗試一次tryAcquire()方法
* 如果調用tryAcquire()失敗了,則將該線程放入等待隊列中,并且會不斷的嘗試tryAcquire()方法,直到返回成功,或者被中斷,或者超時了。
* 該方法可以被用來實現Lock.tryLock(long,TimeUnit)方法
*/
public final boolean tryAcquireNanos(int arg, long nanosTimeout)
        throws InterruptedException {
    if (Thread.interrupted())//檢查線程是否被中斷了
        throw new InterruptedException();
    return tryAcquire(arg) ||
        doAcquireNanos(arg, nanosTimeout);
}

當tryAcquire()方法返回失敗時,會去調用doAcquireNanos()方法,重復嘗試獲取。

/*
* 以獨占超時模式獲取
*/
private boolean doAcquireNanos(int arg, long nanosTimeout)
        throws InterruptedException {
    if (nanosTimeout <= 0L)// 如果超時時間小于0,則直接返回
        return false;
    final long deadline = System.nanoTime() + nanosTimeout;//計算截止時間
    final Node node = addWaiter(Node.EXCLUSIVE);//為當前線程創建一個獨占模式的Node節點,并把Node放入到等待隊列中
    boolean failed = true;
    try {
        for (;;) {
            final Node p = node.predecessor();//前繼節點
            if (p == head && tryAcquire(arg)) {
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return true;
            }
            nanosTimeout = deadline - System.nanoTime();//計算剩余時間
            if (nanosTimeout <= 0L)//超時了,直接返回
                return false;
            if (shouldParkAfterFailedAcquire(p, node) && 
                nanosTimeout > spinForTimeoutThreshold)//在獲取失敗后,如果需要讓線程掛起,則通過LockSupport的parkNanos()方法,讓線程掛起指定的時間
                LockSupport.parkNanos(this, nanosTimeout);
            if (Thread.interrupted())//如果線程被中斷了,則拋出異常
                throw new InterruptedException();
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

doAcquireNanos()方法與acquireQueued()方法實現很類似,不同之處在于,doAcquireNanos()加了一個超時判斷,如果超時了,則直接返回。另外,doAcquireNanos()使用帶超時時間的LockSupport.parkNanos()方法來暫停線程。

2.5.6 tryAcquireSharedNanos()方法

/*
* 嘗試以非獨占模式獲取,如果發生了中斷,則停止,如果超時了,則返回失敗
* 在獲取之前,首先檢查線程是否被中斷過,然后至少嘗試一次tryAcquireShared()方法
* 如果調用tryAcquireShared()失敗了,則將該線程放入等待隊列中,并且會不斷的嘗試tryAcquireShared()方法,直到返回成功,或者被中斷,或者超時了。
*/
public final boolean tryAcquireSharedNanos(int arg, long nanosTimeout)
        throws InterruptedException {
    if (Thread.interrupted())//線程被中斷了
        throw new InterruptedException();
    return tryAcquireShared(arg) >= 0 ||
        doAcquireSharedNanos(arg, nanosTimeout);
}

當tryAcquireShared()方法返回失敗時,會去調用doAcquireSharedNanos()不斷重復嘗試獲取。

/*
* 以非獨占超時模式獲取
*/
private boolean doAcquireSharedNanos(int arg, long nanosTimeout)
        throws InterruptedException {
    if (nanosTimeout <= 0L)// 如果超時時間小于0,則直接返回
        return false;
    final long deadline = System.nanoTime() + nanosTimeout;//計算截止時間
    final Node node = addWaiter(Node.SHARED);//為當前線程創建一個非獨占模式的Node節點,并把Node放入到等待隊列中
    boolean failed = true;
    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
                    failed = false;
                    return true;
                }
            }
            nanosTimeout = deadline - System.nanoTime();//計算剩余時間
            if (nanosTimeout <= 0L)//超時了,直接返回
                return false;
            if (shouldParkAfterFailedAcquire(p, node) &&
                nanosTimeout > spinForTimeoutThreshold) //在獲取失敗后,如果需要讓線程掛起,則通過LockSupport的parkNanos()方法,讓線程掛起指定的時間
                LockSupport.parkNanos(this, nanosTimeout);
            if (Thread.interrupted())
                throw new InterruptedException();
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

doAcquireSharedNanos()方法與doAcquireNanos()方法實現類似,唯一的區別是doAcquireSharedNanos()方法中是以非獨占模式去獲取狀態,調用的是tryAcquireShared()方法去獲取狀態。

至此,已經介紹了所有公開的與acquire相關的final方法。下面看看所有公開的與release相關的final方法。

2.6 AQS類里面定義的release方法

2.6.1 release()方法

/*
* 以獨占模式釋放
* 如果tryRelease()方法返回true,則至少有一個線程非阻塞
* 該方法可以用來實現Lock.unlock()方法
*/
public final boolean release(int arg) {
    if (tryRelease(arg)) {//tryRelease()返回true,則返回true
        Node h = head;//頭結點
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);//喚醒頭結點的后繼節點
        return true;
    }
    return false;
}

可以看到,在release()方法中,首先會調用tryRelease()方法嘗試釋放,如果釋放成功,則返回true;如果釋放失敗,則直接返回false。在tryRelease()方法返回成功后,還會根據頭結點的等待狀態來判斷是否需要喚醒頭結點的后繼節點。

2.6.2 releaseShared()方法

/*
* 以非獨占模式釋放
* 如果tryReleaseShared()方法返回true,則至少有一個線程非阻塞
*/
public final boolean releaseShared(int arg) {
    if (tryReleaseShared(arg)) {//tryReleaseShared()返回true,則返回true
        doReleaseShared();
        return true;
    }
    return false;
}

doReleaseShared()方法在5.3中的acquireShared()方法中有介紹到,其主要是先將頭節點從SIGNAL狀態更新為0,并且喚醒頭結點的后繼節點,然后將頭節點的狀態從0更新為PROPAGATE。

3.AQS的使用

我們通常不是直接繼承AQS類,而是將相應的功能委托為私有的AQS子類來實現。下面是AQS類源碼中介紹的兩個使用范例:

  • 使用范例1

下面是一個不可重入互斥鎖Mutex,使用0代表非鎖定狀態,使用1代表鎖定狀態。雖然不可重入鎖不需要嚴格記錄持有鎖的當前線程,但是在Mutex類中,實現了記錄當前持有鎖的線程,這樣更容易監控。另外,Mutex類也支持Condition條件,并且暴露了一些方法給外部使用。

public class Mutex implements Lock ,Serializable {

    private static class Sync extends AbstractQueuedSynchronizer{
        // Reports whether in locked state
        @Override
        protected boolean isHeldExclusively() {
            return getState() == 1;
        }

        // Acquires the lock if state is zero
        @Override
        protected boolean tryAcquire(int acquires) {
            assert acquires == 1;
            if (compareAndSetState(0,1)){
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            }
            return false;
        }

        // Releases the lock by setting state to zero
        @Override
        protected boolean tryRelease(int releases) {
            assert releases == 1;
            if (getState() == 0){
                throw new IllegalMonitorStateException();
            }
            setExclusiveOwnerThread(null);
            setState(0);
            return true;
        }

        // Provides a Condition
        Condition newCondition(){
            return new ConditionObject();
        }

        // Deserializes properly
        private void readObject(ObjectInputStream s) throws IOException, ClassNotFoundException {
            s.defaultReadObject();
            setState(0); // reset to unlocked state
        }
    }

    // The sync object does all the hard work. We just forward to it.
    private final Sync sync = new Sync();

    @Override
    public void lock() {
        sync.acquire(1);
    }

    @Override
    public void lockInterruptibly() throws InterruptedException {
        sync.acquireInterruptibly(1);
    }

    @Override
    public boolean tryLock() {
        return sync.tryAcquire(1);
    }

    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        return sync.tryAcquireNanos(1,unit.toNanos(time));
    }

    @Override
    public void unlock() {
        sync.release(1);
    }


    @Override
    public Condition newCondition() {
        return sync.newCondition();
    }

    public boolean isLocked(){
        return sync.isHeldExclusively();
    }

    public boolean hasQueuedThreads(){
        return sync.hasQueuedThreads();
    }
}
  • 使用范例2

下面是一個實現類似閉鎖CountDownLatch功能的類,它是以非獨占模式獲取和釋放。

public class BooleanLatch {

    private static class Sync extends AbstractQueuedSynchronizer{
        boolean isSignalled(){
            return getState() != 0;
        }

        @Override
        protected int tryAcquireShared(int ignore) {
            return isSignalled() ? 1 : -1;
        }

        @Override
        protected boolean tryReleaseShared(int ignore) {
            setState(1);
            return true;
        }
    }

    private final Sync sync = new Sync();
    public boolean isSignalled(){
        return sync.isSignalled();
    }

    public void signal(){
        sync.releaseShared(1);
    }

    public void await() throws InterruptedException {
        sync.acquireSharedInterruptibly(1);
    }
}
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,117評論 6 537
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,860評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 177,128評論 0 381
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,291評論 1 315
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,025評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,421評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,477評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,642評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,177評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,970評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,157評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,717評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,410評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,821評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,053評論 1 289
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,896評論 3 395
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,157評論 2 375

推薦閱讀更多精彩內容