深入理解Java中的AQS

LittleSkey發表於2020-10-18

核心思想

AQS就是基於CLH佇列,用volatile修飾共享變數state,執行緒通過CAS去修改狀態符,成功則獲取鎖成功,失敗則進入等待佇列,等待被喚醒。

基本原理

AbstractQueuedSynchronizer抽象佇列同步器簡稱AQS,它是實現同步器的基礎元件,juc下面Lock的實現以及一些併發工具類就是通過AQS來實現的。

  • AQS是一個通過內建的FIFO雙向佇列來完成執行緒的排隊工作,元素的結點型別為Node型別
  • 其中Node中的thread用來存放進入AQS佇列中的執行緒引用,Node結點內部的SHARED表示標記執行緒是因為獲取共享資源失敗被阻塞新增到佇列中的;Node中的EXCLUSIVE表示執行緒因為獲取獨佔資源失敗被阻塞新增到佇列中的。waitStatus表示當前執行緒的等待狀態:
    • CANCELLED=1:表示執行緒因為中斷或者等待超時,需要從等待佇列中取消等待;
    • SIGNAL=-1:當前執行緒thread1佔有鎖,佇列中的head(僅僅代表頭結點,裡面沒有存放執行緒引用)的後繼結點node1處於等待狀態,如果已佔有鎖的執行緒thread1釋放鎖或被CANCEL之後就會通知這個結點node1去獲取鎖執行
    • CONDITION=-2:表示結點在等待佇列中(這裡指的是等待在某個lock的condition上,關於Condition的原理有時間再整理,當持有鎖的執行緒呼叫了Condition的signal()方法之後,結點會從該condition的等待佇列轉移到該lock的同步佇列上,去競爭lock。(注意:這裡的同步佇列就是我們說的AQS維護的FIFO佇列,等待佇列則是每個condition關聯的佇列)
    • PROPAGTE=-3:表示下一次共享狀態獲取將會傳遞給後繼結點獲取這個共享同步狀態。
  • AQS中維持了一個單一的volatile修飾的狀態資訊state(AQS通過Unsafe的相關方法,以原子性的方式由執行緒去獲取這個state)。AQS提供了getState()、setState()、compareAndSetState()函式修改值(實際上呼叫的是unsafe的compareAndSwapInt方法)。
  • AQS的設計師基於模板方法模式的。使用時候需要繼承同步器並重寫指定的方法,並且通常將子類推薦為定義同步元件的靜態內部類,子類重寫這些方法之後,AQS工作時使用的是提供的模板方法,在這些模板方法中呼叫子類重寫的方法。其中子類可以重寫的方法:
    //獨佔式的獲取同步狀態,實現該方法需要查詢當前狀態並判斷同步狀態是否符合預期,然後再進行CAS設定同步狀態
    protected boolean tryAcquire(int arg) {	throw new UnsupportedOperationException();}
    //獨佔式的釋放同步狀態,等待獲取同步狀態的執行緒可以有機會獲取同步狀態
    protected boolean tryRelease(int arg) {	throw new UnsupportedOperationException();}
    //共享式的獲取同步狀態
    protected int tryAcquireShared(int arg) { throw new UnsupportedOperationException();}
    //嘗試將狀態設定為以共享模式釋放同步狀態。 該方法總是由執行釋放的執行緒呼叫。 
    protected int tryReleaseShared(int arg) { throw new UnsupportedOperationException(); }
    //當前同步器是否在獨佔模式下被執行緒佔用,一般該方法表示是否被當前執行緒所獨佔
    protected int isHeldExclusively(int arg) {	throw new UnsupportedOperationException();}

     

  • AQS的內部類ConditionObject是通過結合鎖實現執行緒同步,ConditionObject可以直接訪問AQS的變數(state、queue),ConditionObject是個條件變數 ,每個ConditionObject對應一個佇列用來存放執行緒呼叫condition條件變數的await方法之後被阻塞的執行緒。

ReentrantLock非公平鎖加鎖過程

執行緒一獲取鎖

在初始情況下,執行緒一呼叫了lock方法請求獲得鎖,首先通過CAS的方式將state更新為1,表示自己執行緒一獲得了鎖,並將獨佔鎖的執行緒持有者設定為執行緒一

final void lock() {
    if (compareAndSetState(0, 1))
        //setExclusiveOwnerThread是AbstractOwnableSynchronizer的方法,AQS繼承了AbstractOwnableSynchronizer
        setExclusiveOwnerThread(Thread.currentThread());
    else
        acquire(1);
}

執行緒二搶佔鎖失敗

此時執行緒二呼叫lock方法,嘗試通過CAS更新state,更新失敗(執行緒一已經更新成功)。呼叫acquire(1)。

//(1)tryAcquire,這裡執行緒二執行返回了false,那麼就會執行addWaiter將當前執行緒構造為一個結點加入同步佇列中
public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

 非公平鎖的實現中,AQS的模板方法acquire(1)就會呼叫NofairSync的tryAcquire方法,而tryAcquire方法又呼叫的Sync的  nonfairTryAcquire方法,所以我們看看nonfairTryAcquire的流程

//NofairSync
protected final boolean tryAcquire(int acquires) {
    return nonfairTryAcquire(acquires);
}
final boolean nonfairTryAcquire(int acquires) {
    //(1)獲取當前執行緒
    final Thread current = Thread.currentThread();
    //(2)獲得當前同步狀態state
    int c = getState();
    //(3)如果state==0,表示沒有執行緒獲取
    if (c == 0) {
        //(3-1)那麼就嘗試以CAS的方式更新state的值
        if (compareAndSetState(0, acquires)) {
            //(3-2)如果更新成功,就設定當前獨佔模式下同步狀態的持有者為當前執行緒
            setExclusiveOwnerThread(current);
            //(3-3)獲得成功之後,返回true
            return true;
        }
    }
    //(4)這裡是重入鎖的邏輯
    else if (current == getExclusiveOwnerThread()) {
        //(4-1)判斷當前佔有state的執行緒就是當前來再次獲取state的執行緒之後,就計算重入後的state
        int nextc = c + acquires;
        //(4-2)這裡是風險處理
        if (nextc < 0) // overflow
            throw new Error("Maximum lock count exceeded");
        //(4-3)通過setState無條件的設定state的值,(因為這裡也只有一個執行緒操作state的值,即
        //已經獲取到的執行緒,所以沒有進行CAS操作)
        setState(nextc);
        return true;
    }
    //(5)沒有獲得state,也不是重入,就返回false
    return false;
}

插入佇列

嘗試獲取失敗,呼叫addWaiter方法執行建立節點插入佇列操作

private Node addWaiter(Node mode) {
    //(1)將當前執行緒以及阻塞原因(是因為SHARED模式獲取state失敗還是EXCLUSIVE獲取失敗)構造為Node結點
    Node node = new Node(Thread.currentThread(), mode);
    //(2)這一步是快速將當前執行緒插入佇列尾部
    Node pred = tail;
    if (pred != null) {
        //(2-1)將構造後的node結點的前驅結點設定為tail
        node.prev = pred;
        //(2-2)以CAS的方式設定當前的node結點為tail結點
        if (compareAndSetTail(pred, node)) {
            //(2-3)CAS設定成功,就將原來的tail的next結點設定為當前的node結點。這樣這個雙向佇列就更新完成了
            pred.next = node;
            return node;
        }
    }
    //(3)執行到這裡,說明要麼當前佇列為null,要麼存在多個執行緒競爭失敗都去將自己設定為tail結點,
    //那麼就會有執行緒在上面(2-2)的CAS設定中失敗,就會到這裡呼叫enq方法
    enq(node);
    return node;
}

private Node enq(final Node node) {
    for (;;) {
        //(4)還是先獲取當前佇列的tail結點
        Node t = tail;
        //(5)如果tail為null,表示當前同步佇列為null,就必須初始化這個同步佇列的head和tail(建
        //立一個哨兵結點)
        if (t == null) { 
            //(5-1)初始情況下,多個執行緒競爭失敗,在檢查的時候都發現沒有哨兵結點,所以需要CAS的
            //設定哨兵結點
            if (compareAndSetHead(new Node()))
                tail = head;
        } 
        //(6)tail不為null
        else {
            //(6-1)直接將當前結點的前驅結點設定為tail結點
            node.prev = t;
            //(6-2)前驅結點設定完畢之後,還需要以CAS的方式將自己設定為tail結點,如果設定失敗,
            //就會重新進入迴圈判斷一遍
            if (compareAndSetTail(t, node)) {
                t.next = node;
                return t;
            }
        }
    }
}

第一次迴圈tail指標為空,新建Node節點。此時AQS中資料

第二次迴圈建立執行緒二對應的Node節點掛到head節點後

再次嘗試獲取鎖,失敗掛起執行緒

final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        boolean interrupted = false;
        //在這樣一個迴圈中嘗試tryAcquire同步狀態
        for (;;) {
            //獲取前驅結點
            final Node p = node.predecessor();
            //(1)如果前驅結點是頭節點,就嘗試取獲取同步狀態,這裡的tryAcquire方法相當於還是調
            //用NofairSync的tryAcquire方法,在上面已經說過
            if (p == head && tryAcquire(arg)) {
                //如果前驅結點是頭節點並且tryAcquire返回true,那麼就重新設定頭節點為node
                setHead(node);
                p.next = null; //將原來的頭節點的next設定為null,交由GC去回收它
                failed = false;
                return interrupted;
            }
            //(2)如果不是頭節點,或者雖然前驅結點是頭節點但是嘗試獲取同步狀態失敗就會將node結點
            //的waitStatus設定為-1(SIGNAL),並且park自己,等待前驅結點的喚醒。至於喚醒的細節
            //下面會說到
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}


private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    //(1)獲取前驅結點的waitStatus
    int ws = pred.waitStatus;
    //(2)如果前驅結點的waitStatus為SINGNAL,就直接返回true
    if (ws == Node.SIGNAL)
        //前驅結點的狀態為SIGNAL,那麼該結點就能夠安全的呼叫park方法阻塞自己了。
        return true;
    if (ws > 0) {
        //(3)這裡就是將所有的前驅結點狀態為CANCELLED的都移除
        do {
            node.prev = pred = pred.prev;
        } while (pred.waitStatus > 0);
        pred.next = node;
    } else {
        //CAS操作將這個前驅節點設定成SIGHNAL。
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
    }
    return false;
}

private final boolean parkAndCheckInterrupt() {
    LockSupport.park(this);
    return Thread.interrupted();
}

acquireQueued()方法先判斷當前傳入Node前置節點是否為head,是則嘗試加鎖。加鎖成功將當前節點設定為head節點,然後空置之前的head節點,方便垃圾回收掉。

如果加鎖失敗或者Node的前置節點不是head節點,通過shouldParkAfterFailedAcquire方法 將head節點的waitStatus變為了SIGNAL=-1,最後執行parkAndChecknIterrupt方法,呼叫LockSupport.park()掛起當前執行緒

此時執行緒二就靜靜的待在AQS的等待佇列裡面了,等著其他執行緒釋放鎖來喚醒它

執行緒三搶佔鎖失敗,插入佇列

非公平鎖的釋放流程

 unlock方法,實際呼叫了AQS的release()方法

public void unlock() {
    sync.release(1); //這裡ReentrantLock的unlock方法呼叫了AQS的release方法
}
public final boolean release(int arg) {
	//這裡呼叫了子類的tryRelease方法,即ReentrantLock的內部類Sync的tryRelease方法
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }
    return false;
}

釋放鎖

protected final boolean tryRelease(int releases) {
    //(1)獲取當前的state,然後減1,得到要更新的state
    int c = getState() - releases;
    //(2)判斷當前呼叫的執行緒是不是持有鎖的執行緒,如果不是丟擲IllegalMonitorStateException
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    boolean free = false;
    //(3)判斷更新後的state是不是0
    if (c == 0) {
        free = true;
        //(3-1)將當前鎖持者設為null
        setExclusiveOwnerThread(null);
    }
    //(4)設定當前state=c=getState()-releases
    setState(c);
    //(5)只有state==0,才會返回true
    return free;
}

喚醒結點

private void unparkSuccessor(Node node) {
    //(1)獲得node的waitStatus
    int ws = node.waitStatus;
    //(2)判斷waitStatus是否小於0
    if (ws < 0)
        //(2-1)如果waitStatus小於0需要將其以CAS的方式設定為0
        compareAndSetWaitStatus(node, ws, 0);

    //(2)獲得s的後繼結點,這裡即head的後繼結點
    Node s = node.next;
    //(3)判斷後繼結點是否已經被移除,或者其waitStatus==CANCELLED
    if (s == null || s.waitStatus > 0) {
        //(3-1)如果s!=null,但是其waitStatus=CANCELLED需要將其設定為null
        s = null;
        //(3-2)會從尾部結點開始尋找,找到離head最近的不為null並且node.waitStatus<=0的結點
        for (Node t = tail; t != null && t != node; t = t.prev)
            if (t.waitStatus <= 0)
                s = t;
    }
    //(4)node.next!=null或者找到的一個離head最近的結點不為null
    if (s != null)
        //(4-1)喚醒這個結點中的執行緒
        LockSupport.unpark(s.thread);
}

總體流程

參考文章

https://www.cnblogs.com/fsmly/p/11274572.html

https://blog.csdn.net/qq_18221459/article/details/106317155?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.add_param_isCf&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.add_param_isCf

相關文章