源碼分析AbstractQuenedSynchronized(二)

源碼分析AbstractQuenedSynchronized(一)
源碼分析AbstractQuenedSynchronized(三)

主要分析AbstractQuenedSynchronized的Condition接口實現類ConditionObject,包括await方法和signal方法

Condition

Condition的產生通過ReentrantLock實例的newCondition方法,而實現ReentrantLock功能的內部類Sync又是AbstractQuenedSynchronized的實現類,由此可以看出:Condition依賴於ReentrantLock實現,而ReentrantLock又依賴於AbstractQuenedSynchronized實現.
在這裏插入圖片描述

		//##ReentrantLock類
        final ConditionObject newCondition() {
        //實例化一個ConditionObject
            return new ConditionObject();
        }
        
    //##AbstractQuenedSynchronized類
    public class ConditionObject implements Condition, java.io.Serializable {
        /** 條件隊列的第一個結點 */
        private transient Node firstWaiter;
        /** 條件隊列的最後一個結點 */
        private transient Node lastWaiter;

        public ConditionObject() { }
	}		

由Condition的實現類ConditionObject我們可以看出由兩個屬性:firstWaiter和lastWaiter,它們都是Node。
在上一篇介紹 AQS 的時候,我們有一個阻塞隊列(sync queue),用於保存等待獲取鎖的線程的隊列。這裏我們引入另一個概念,叫條件隊列(condition queue),如下圖所示。
圖片源於https://javadoop.com/post/AbstractQueuedSynchronizer-2
圖片源於https://javadoop.com/post/AbstractQueuedSynchronizer-2

從圖中,我們首先需要了解的是:

  1. 條件隊列和阻塞隊列的結點都是Node類型,因爲條件隊列的結點是要轉移到阻塞隊列中的
  2. Condition的實例是通過ReentrantLock的newCondition方法產生的,一個newCondition產生一個新的Condition實例
  3. 每一個Condition實例都有一個關聯的條件隊列,由上面的ConditionObject 類我們也可以看出,只有兩個屬性:條件隊列的頭結點和末尾結點。如線程 1 調用 condition1.await() 方法即可將當前線程 1 包裝成 Node 後加入到條件隊列中,然後阻塞在這裏,不繼續往下執行,條件隊列是一個單向鏈表
  4. 調用condition1.signal() 觸發一次喚醒,此時喚醒的是隊頭,會將condition1 對應的條件隊列的 firstWaiter(隊頭) 移到阻塞隊列的隊尾,等待獲取鎖,獲取鎖後 await 方法才能返回,繼續往下執行。

await方法

//##############AbstractQuenedSynchronized類中的ConditionObject類######################## 
        public final void await() throws InterruptedException {
            //1.首先判斷線程狀態是否中斷
            if (Thread.interrupted())
                //中斷則直接拋異常
                throw new InterruptedException();
            //2.將當前線程封裝成狀態爲CONDITION的Node並添加到條件隊列隊尾
            Node node = addConditionWaiter();
            
/*======================================================================
 private Node addConditionWaiter() {
            Node t = lastWaiter;
            // If lastWaiter is cancelled, clean out.
            if (t != null && t.waitStatus != Node.CONDITION) {
                // 將隊列中狀態不爲CONDITION的結點移除
                unlinkCancelledWaiters();
                t = lastWaiter;
            }
            Node node = new Node(Thread.currentThread(), Node.CONDITION);
            if (t == null)
                firstWaiter = node;
            else
                t.nextWaiter = node;
            lastWaiter = node;
            return node;
        }
======================================================================*/

            //3.調用await方法之前,當前線程必須要持有鎖,然後釋放鎖,這裏要完全釋放獨佔鎖
            int savedState = fullyRelease(node);//savedState爲釋放鎖之前的state
            
/*======================================================================
    final int fullyRelease(Node node) {
        boolean failed = true;
        try {
            int savedState = getState();//返回的savedState爲釋放鎖之前的state
            //釋放鎖
            //如果一個線程沒有獲得鎖(沒有執行lock方法)就執行condition.await()方法,該線程結點會進入等待隊列然後執行release方法拋出異常,同時node狀態變爲cancelled,這個已經入隊的結點在後面會被後續結點“請出去”
            
            if (release(savedState)) {
                failed = false;
                return savedState;
            } else {
                throw new IllegalMonitorStateException();
            }
        } finally {
            if (failed)
                //當釋放鎖失敗時
                node.waitStatus = Node.CANCELLED;
        }
    }
======================================================================*/

            int interruptMode = 0;
            //4. 當線程結點不在阻塞隊列時將線程掛起
            /*退出當前循環有兩種情況:一、當前線程node轉移到阻塞隊列中 二、線程中斷*/
            while (!isOnSyncQueue(node)) 
            
/*======================================================================
    // 當一個node從條件隊列轉移到阻塞隊列時返回true
    final boolean isOnSyncQueue(Node node) {
        if (node.waitStatus == Node.CONDITION || node.prev == null)
            return false;
        //有後繼結點,說明一定在阻塞隊列
        if (node.next != null) // If has successor, it must be on queue
            return true;
        //不能通過node.prev!=null來判斷node在阻塞隊列,因爲CAS操作將自己設爲新的tail過程中可能會失敗,因此該方法findNodeFromTail從tail往前遍歷看能不能在阻塞隊列中找到該node
        return findNodeFromTail(node);
    }
======================================================================*/        
  
			{
                //當前線程結點不在阻塞隊列中,當前線程掛起
                LockSupport.park(this);
				/*========================
5 有以下三種情況會讓 LockSupport.park(this); 這句返回繼續往下執行:
(1)常規路徑。signal -> 轉移節點到阻塞隊列 -> 獲取了鎖(unpark)
(2)線程中斷。在 park 的時候,另外一個線程對這個線程進行了中斷
(3)signal 的時候我們說過,轉移以後的前驅節點取消了,或者對前驅節點的CAS操作失敗了
				==========================*/
                //邏輯走到這裏,說明線程被喚醒,檢查中斷狀態
                if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
                    break;
            }
           
/*======================================================================
 interruptMode 可以取值爲 REINTERRUPT(1),THROW_IE(-1),0
         // 1. 如果在 signal 之前已經中斷,返回 THROW_IE
         // 2. 如果是 signal 之後中斷,返回 REINTERRUPT
         // 3. 沒有發生中斷,返回 0
	        private int checkInterruptWhileWaiting(Node node) {
            return Thread.interrupted() ?
                (transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) :
                0;
        }
        
    final boolean transferAfterCancelledWait(Node node) {
        //由於Signal操作將node的WaitStatus設爲0,所以如果下面CAS成功則說明中斷髮生在signal方法前;CAS失敗說明發生在signal方法後
        if (compareAndSetWaitStatus(node, Node.CONDITION, 0)) {
            //即使中斷,也會轉移到阻塞隊列
            //這裏描繪了一個場景,本來有個線程,它是排在條件隊列的後面的,但是因爲它被中斷了,那麼它會被喚醒,然後它發現自己不是被 signal 的那個,但是它會自己主動去進入到阻塞隊列。
            enq(node);
            return true;
        }

        //邏輯走到這裏說明中斷髮生在signal方法後,但是signal方法可能還沒有結束,因此這邊自旋等待其完成
        while (!isOnSyncQueue(node))
            Thread.yield();
        return false;
    }
======================================================================*/
           
            //6.被喚醒後當前線程node進入阻塞隊列,等待獲取鎖
            if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
                //acquireQueued(node, savedState) 的返回值就是代表線程是否被中斷。邏輯到這裏,說明線程在signal方法後發生中斷
                interruptMode = REINTERRUPT;//將狀態設爲REINTERRUPT,用於待會重新中斷
            if (node.nextWaiter != null) // clean up if cancelled
            //signal方法會將結點從條件隊列轉移到阻塞隊列,同時斷開node和後面結點的聯繫,邏輯走到這裏,說明signal之前發生了中斷,也需要將節點進行轉移到阻塞隊列,這部分轉移的時候,是沒有設置 node.nextWaiter = null 的。
                unlinkCancelledWaiters();
            if (interruptMode != 0)
                reportInterruptAfterWait(interruptMode);
                
/*======================================================================
private void reportInterruptAfterWait(int interruptMode)
    throws InterruptedException {
    //THROW_IE則拋異常,REINTERRUPT則重新中斷(這裏中斷的含義是設置一個線程的中斷狀態爲true,並沒有拋異常!)
    if (interruptMode == THROW_IE)
        throw new InterruptedException();
    else if (interruptMode == REINTERRUPT)
        selfInterrupt();//  Thread.currentThread().interrupt();
}
======================================================================*/ 
                 
    }


注意到上述await方法的步驟5有三種情況會讓 LockSupport.park(this)方法返回,除了響應中斷就是利用正常途徑signal方法了,下面開始分析Signal方法


signal方法

//##承接await方法的步驟4:LockSupport.park(this); 
//喚醒等待最久的線程,將這個線程node從條件隊列中轉移到阻塞隊列中
        public final void signal() {
         // 調用 signal 方法的線程必須持有當前的獨佔鎖
            if (!isHeldExclusively())
                throw new IllegalMonitorStateException();
            Node first = firstWaiter;
            if (first != null)
                doSignal(first);
        }    

/*======================================================================
        private void doSignal(Node first) {
            do {
            //將firstWaiter 指向下一個Waiter,因爲它就要轉移到阻塞隊列了
                if ( (firstWaiter = first.nextWaiter) == null)
                    lastWaiter = null;
                //first即將離開,斷開和後面的關係
                first.nextWaiter = null;
            } while (!transferForSignal(first) &&
                     (first = firstWaiter) != null);//這裏 while 循環,如果 first 轉移不成功,那麼選擇 first 後面的第一個節點進行轉移,依此類推
        }
    
    //返回return代表:成功將node轉移到阻塞隊列       false:該節點已經取消,不需要轉移了    
    final boolean transferForSignal(Node node) {
    // CAS 如果失敗,說明此 node 的 waitStatus 已不是 Node.CONDITION,說明節點已經取消,
    // 既然已經取消,也就不需要轉移了,方法返回,轉移後面一個節點
    // 否則,將 waitStatus 置爲 0
        if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
            return false;
        Node p = enq(node);//自旋進入阻塞隊列的隊尾,p爲node在阻塞隊列的前驅結點
        int ws = p.waitStatus;
       
        if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
            //正常情況下不會進入該邏輯,但是如果前驅結點取消或者CAS設置前驅結點爲SINGAL失敗則喚醒當前線程,直接進入步驟4的下一步
            LockSupport.unpark(node.thread);
        return true;
    }
======================================================================*/    

總結

Conditiond的await和signal過程:
await
1.首先判斷線程狀態是否中斷
2.將當前線程封裝成狀態爲CONDITION的Node並添加到條件隊列隊尾
3.釋放鎖,這裏要完全釋放獨佔鎖(調用await方法之前,當前線程必須要持有鎖,否則在嘗試釋放鎖時就會拋異常)
4. 當線程結點不在阻塞隊列時將線程掛起
5.signal方法將這個線程node從條件隊列中轉移到阻塞隊列中(轉移到阻塞隊列的方式有兩個:signal方式和中斷方式)
6.在阻塞隊列中等待重新獲取鎖

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章