AbstractQueuedSynchronizer原理剖析

爬蜥發表於2019-01-19

無論是公平鎖還是非公平鎖,它們的實現都依賴於AbstractQueuedSynchronizer,它提供了一個基於先進先出等待佇列 實現block locks和synchronizers的框架。特性如下

  • 僅通過一個 int 型別來代表狀態。對於ReentrantLock而言,他就是執行緒持有鎖的次數,當次數為0時,代表鎖沒有被持有,正數代表被持有的次數,負數則是超出了鎖的持有範圍,有可能存在死迴圈
  • 支援獨佔模式(預設的)和共享模式。在獨佔模式中,去獲取一個已經被其它執行緒擁有的鎖只會失敗,共享模式中,則多個執行緒是可以成功的。

lock()原理

當ReentrantLock獲取鎖失敗時,會執行 acquireQueued(addWaiter(Node.EXCLUSIVE), arg)

private Node addWaiter(Node mode) { 
    Node node = new Node(Thread.currentThread(), mode); //建立一個節點,儲存當前的執行緒,以及鎖持有的模式,對於 ReentrantLock來說就是 獨佔 型
    // Try the fast path of enq; backup to full enq on failure    
    Node pred = tail;
    if (pred != null) {
        node.prev = pred;
        if (compareAndSetTail(pred, node)) {//CAS操作,如果當前的尾部節點沒有被其它執行緒更改,那麼把新的節點設定成佇列的尾部
            pred.next = node;
            return node;
        }
    }
    enq(node);//首次入隊
    return node;
}

獲取失敗進行入隊操作,首先就是往佇列中新增一個正在等待的節點Node

圖片描述
從Node本身的結構可以看到,AQS(AbstractQueuedSynchronizer)本身就維護了一個雙向連結串列,用來存放等待中的執行緒。連結串列的每個節點,代表那個執行緒,是獨佔還是共享鎖。
建立好節點之後,便執行入隊操作,對於首次建立佇列

private Node enq(final Node node) {
    for (;;) {
       //藉助CAS機制實現無鎖操作,所以需要一直執行直到CAS成功
        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;
            }
        }
    }
}

可以看到,入隊也就是從隊尾插入新的等待執行緒,入隊完畢,也就開始去進行不斷的嘗試,直到獲取鎖成功,可以看到,對於lock來說,其實已經是阻塞了

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,才去獲取執行緒,這裡可以看出其實先等待的執行緒是會優先處理,也就是FIFO原則
                setHead(node); 
                p.next = null; // help GC    ,釋放掉當前執行緒在佇列中的引用,也可以看做’出隊`了            
                failed = false;
                //執行到這裡說明獲取鎖成功
                return interrupted;
            }
            //執行到這裡說明存在競爭,有多個執行緒都在等待一個鎖
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt()) //裡面會對當前執行緒執行中斷,當被喚醒時,繼續迴圈
                    //如果執行緒被中斷,設定中斷標記,區別於 doAcquireInterruptibly,doAcquireInterruptibly是直接丟擲異常,這也就是 lockInterruptibly能夠丟擲中斷的原因
                    interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

從這裡可以看到,無論鎖是公平鎖還是非公平鎖,只要被放入了等待佇列,此時的執行依然是誰先等待就先執行誰 ,非公平鎖體現在新來的執行緒會無視已經等了的執行緒,可以優先去搶鎖,所以公平體現在第一次參與搶鎖的執行緒會去等待已經在等待佇列中的執行緒,非公平並不是說從已經在等待的執行緒佇列裡面隨便選一個

shouldParkAfterFailedAcquire的原始碼如下

private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    int ws = pred.waitStatus; //檢視前一個節點的等待狀態
    if (ws == Node.SIGNAL)
            //已經嘗試過獲取鎖,可以執行park了
            return true;
    if (ws > 0) {   
        do {
            //去掉佇列中所有已經取消的執行緒
            node.prev = pred = pred.prev;
        } while (pred.waitStatus > 0);
        pred.next = node;
    } else {             
        //此時當前執行緒的前一個節點的等待狀態必定是0或者PROGATE,這表明當前執行緒在park之前可以再嘗試一次去獲取鎖,也就是說前一個節點可能剛獲取到SIGNAL
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
    }
    return false;
}

waitStatus:等待的狀態,共有5種

  • SIGNAL:,表明它的前一個節點需要執行 unparking;
  • CANCELLED:當前節點儲存的執行緒由於超時或者中斷被取消了;
  • CONDITION:接檔正處於條件佇列中,執行了await;
  • PROPAGATE:一個共享的鎖需要傳遞釋放訊號到其它節點
  • 0:非上述4中狀態,有可能是剛獲取signal,此時它的值是0,也有可能是新建的head節點

parkAndCheckInterrupt主要是park當前執行緒

private final boolean parkAndCheckInterrupt() {
    //當獲取不到許可時,阻塞執行緒,解除阻塞狀態的情況如下:
    //1 某個執行緒對這個執行緒呼叫了unpark方法
    //2 某個執行緒中斷了這個執行緒
    //3 這個方法毫無理由的返回了 [park比較奇特的地方],基於這樣,呼叫的時候必須去判斷park的條件,以及當它返回的時候,去設定中斷的狀態
    LockSupport.park(this); 
    //返回執行緒的中斷狀態
    return Thread.interrupted();
}

至此lock()執行結束

unlock()原理

當執行unlock時,ReentrentLock執行對應的Release

public final boolean release(int arg) {
    if (tryRelease(arg)) {
        //執行這裡表示所已經被釋放,可以讓它的下一個節點來搶鎖
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h); //h.waitStatus == 0 表示還沒有執行park,自然不需要unpark
        return true;
    }
    return false;
}

如果release成功,即當前執行緒持有的所有鎖都已經釋放,那麼就可以執行 unparkSuccessor,從原始碼可以看到,unpark是從頭部開始進行的,結合lock的原理,可知AQS本身就是一個先進先出的佇列
unparkSuccessor原始碼如下

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); //恢復執行緒
}

至此unlock()完畢

await的原理

public final void await() throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();//當前執行緒已經中斷了,丟擲中斷異常
    //新增一個新的waiter到condition queue中,這個新的Node的waitStatus會被標記為CONDITION
    Node node = addConditionWaiter(); 
    //釋放當前執行緒擁有的鎖,即從sync queue中去掉當前執行緒
    int savedState = fullyRelease(node);
    int interruptMode = 0;
    while (!isOnSyncQueue(node)) {
    //如果當前執行緒不在持有鎖的佇列裡頭,對他進行休眠,當其它執行緒執行 unlock的時候,釋放鎖,就會執行unpark操作,此時它會被喚醒,喚醒後,如果它在syn佇列裡頭,開始繼續往下執行。(這個插入操作則是由signal完成)
        LockSupport.park(this); 
        if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
            break;//等待的過程中執行緒中斷了,退出
    }
//重新競爭鎖,相當於執行了lock操作
    if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
    //再次去獲取鎖,如果當前的執行緒在park的時候是被中斷了,並且ConditionObject並不是由於中斷返回,這裡再次標記為中斷
        interruptMode = REINTERRUPT;  
    if (node.nextWaiter != null) 
    //清除非Condition模式的執行緒,而在signal中有先關操作將conditon的執行緒設定成非condition
         unlinkCancelledWaiters();
    if (interruptMode != 0)
    //上報等待的過程中發生了中斷,如果是要丟擲中斷,就丟擲,否則再次執行中斷
        reportInterruptAfterWait(interruptMode); 
}

isOnSyncQueue原始碼如下

final boolean isOnSyncQueue(Node node) {
    if (node.waitStatus == Node.CONDITION || node.prev == null)
    //node本身是呼叫了 await 方法,或者沒有在獲取鎖的佇列裡頭,[如果在裡頭必定有一個前置的節點]
        return false; 
    if (node.next != null) 
    //當前節點存在下一個節點,那麼它肯定是執行過 enq ,即獲取過鎖
        return true; 
    // CAS失敗的時候,有可能 node.rev是沒有的,因此需要從頭到尾遍歷一次
   return findNodeFromTail(node); 
}

checkInterruptWhileWaiting原始碼如下

private int checkInterruptWhileWaiting(Node node) {
    return Thread.interrupted() ?
        (transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) :
        0;
}
final boolean transferAfterCancelledWait(Node node) {
    if (compareAndSetWaitStatus(node, Node.CONDITION, 0)) {
    //執行緒中斷重新獲取鎖,並且設定waitStatus為0,以便後續執行緒從condition queue清除
        enq(node); 
        return true;
    }
    while (!isOnSyncQueue(node))
    //如果CAS失敗,只要當前節點沒有在Sync queue中,那麼一直自旋,每次都會交出執行許可權
        Thread.yield(); 
    return false;
}

可以看到,await其實就是釋放執行緒原有的鎖,並把它放入conditon佇列中,然後執行阻塞。等喚醒的時候,重新獲取鎖,並清掉condition queue中的執行緒。 至此await執行結束

singnal的原理

public final void signal() {
    if (!isHeldExclusively()) //只有當前執行緒持有了鎖,才能釋放
        throw new IllegalMonitorStateException();
    Node first = firstWaiter;
    if (first != null)
        doSignal(first);//優先釋放佇列頭的,也就是等待時間最長的condition node
}
private void doSignal(Node first) {
    do {
        if ( (firstWaiter = first.nextWaiter) == null)
            lastWaiter = null;
        first.nextWaiter = null;
    } while (!transferForSignal(first) &&
             (first = firstWaiter) != null);
}
//將節點從condition queue轉移到sync queue
final boolean transferForSignal(Node node) {
    if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
        return false; //設定為非等待失敗,則不繼續轉移
//CAS設定等待狀態為0成功
Node p = enq(node); //新節點放入sync queue,並返回原來的尾部節點,也就是新節點的前一個節點
    int ws = p.waitStatus;
    if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL)) //參考shouldParkAfterFailedAcquire
        LockSupport.unpark(node.thread);//如果當前節點的前一個節點執行緒已經取消,或者將當前節點的前一個節點執行緒的waitStatus設定成SIGNAL失敗,則直接喚醒當前執行緒
    return true;
}

可以看到signal最關鍵的資訊就是去掉等待佇列中的CONDITION狀態,並將執行緒加入sync佇列,至此signal結束

相關文章