Java之AQS(三)


前言

上篇文章 中,我們分析了同步隊列的節點如何new出條件隊列,條件隊列節點又是如何跑到同步隊列中去的。

而在分析同步隊列的節點如何new出條件隊列的時候,我們從 await方法開始分析,直到了 isOnSyncQueue方法。而本篇內容將接着繼續分析,掛起的線程喚醒後的過程。

public final void await() throws InterruptedException {
            // 如果當前線程被中斷過, 則直接拋出中斷異常
            if (Thread.interrupted())
                throw new InterruptedException();
            // 封裝當前線程, 並扔到條件隊列中
            Node node = addConditionWaiter();
            // 完全釋放當前線程佔用的鎖, 並保存釋放前(即當前)的鎖狀態
            int savedState = fullyRelease(node);
            int interruptMode = 0;
            // 如果當前節點(封裝好的線程)不在同步隊列中
            // 說明還沒有被signal過
            while (!isOnSyncQueue(node)) {
                // 掛起
                LockSupport.park(this);
                // 講道理, 上面執行後不應該執行到這裏的
                // 當如果線程被中斷了、或者被signal了, 
                // 則會跑到這裏
                // 檢查換新的原因, 如果是中斷則跳出循環
                if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
                    break;
            }
            // 下面這部分代碼先不用管
            if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
                interruptMode = REINTERRUPT;
            if (node.nextWaiter != null) // clean up if cancelled
                unlinkCancelledWaiters();
            if (interruptMode != 0)
                reportInterruptAfterWait(interruptMode);
        }


阻塞的線程醒過來了

當條件隊列的節點不在同步隊列時,我們接下的操作是計劃將當前線程掛起,既然掛起了,下面的程序又是什麼時候執行呢?

  while (!isOnSyncQueue(node)) {
                // 掛起
                LockSupport.park(this);
                // 講道理, 上面執行後不應該執行到這裏的
                // 當如果線程被中斷了、或者被signal了, 
                // 則會跑到這裏
                // 檢查喚醒的原因, 如果是中斷則跳出循環
                if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
                    break;
            }
  if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
     interruptMode = REINTERRUPT;
 if (node.nextWaiter != null) // clean up if cancelled
        unlinkCancelledWaiters();
  if (interruptMode != 0)
       reportInterruptAfterWait(interruptMode);

這裏有兩種情況:

1.沒有中斷、有對應的ConditionObject調用了signal方法進行喚醒操作。

2. 休眠的線程被中斷了、被迫喚醒。


checkInterruptWhileWaiting

所以,這裏既然醒了,我們需要檢查一下醒來的原因,以便判斷接下的路該如何走。於是就有了這一行代碼的判斷:

 if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)

關於checkInterruptWhileWaiting方法的源碼如下,它會判斷當前線程是否發生中斷,沒有的話取值爲0(被正常的signal),有的話,需要檢查一下中斷的原因。

 /**
   * Checks for interrupt, returning THROW_IE if interrupted
   * before signalled, REINTERRUPT if after signalled, or
   * 0 if not interrupted.
 */
   private int checkInterruptWhileWaiting(Node node) {
      return Thread.interrupted() ?
         (transferAfterCancelledWait(node) ? 
         THROW_IE : REINTERRUPT) : 0;
   }

interruptMode = 0

先說說interruptMode 爲0的情況吧,如果爲0,沒有中斷,繼續外層 的判斷,即while (!isOnSyncQueue(node))

爲什麼需要上面的判斷呢?根據我們前面對signal方法的分析,喚醒的線程可能還沒來得及跑到同步隊列中,也就是說線程可能處於“假喚醒”的狀態,處理的方式也比較簡單,就加上while (!isOnSyncQueue(node))的循環判斷,正常醒來的線程沒有在同步隊列中,那就仍然在等待隊列中,繼續讓它睡覺。

如果已經在同步隊列中了,跳出while循環、然後執行如下部分的源碼,前面分析acquireQueued時候,我們可以知道它就是 阻塞式的獲取鎖。

如果acquireQueued返回false表示沒有發生中斷,如果返回true則表示在搶鎖的過程中發生了中斷。當然了,由於interruptMode == 0這裏interruptMode = REINTERRUPT會被執行到。

 if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
     interruptMode = REINTERRUPT;
 if (node.nextWaiter != null) // clean up if cancelled
        unlinkCancelledWaiters();
  if (interruptMode != 0)
       reportInterruptAfterWait(interruptMode);

接着判斷如果node.nextWaiter不爲null,會清除已經處於Cancel狀態的節點。最後,在源碼中進行自我中斷。

 private void reportInterruptAfterWait(int interruptMode)
            throws InterruptedException {
            if (interruptMode == THROW_IE)
                throw new InterruptedException();
            else if (interruptMode == REINTERRUPT)
                selfInterrupt();
        }

interruptMode != 0

interruptMode != 0表示有中斷髮生,根據如下源碼的註釋,我們可以知道,中斷也是需要分爲兩種情況的,一種是在喚醒signal之前發生中斷,此時將interruptMode 設置爲THROW_IE ,另一種則是在signal之後(已發生signal)發生中斷,此時將interruptMode 設置爲REINTERRUPT 。

這裏可能比較難理解,很多人覺得如果是signal後,那麼Thread.interrupted() ?判斷時不應該返回0嗎。其實這種思考陷入了一個單線程的思維模式裏。多線程之下,A調用signa和B發生中斷,是兩個維度的東西,沒有商量的情況下,誰也不能保證B的中斷是發生在A的signal之前、還是之後。

關於interruptMode,先做一下總結,它初始值爲0,它的取值有三個:

  1. REINTERRUPT:1 // 表示線程從等待退出後需要彌補一下中斷

  2. THROW_IE :-1 // 表示線程從等待退出後需要拋出中斷

  3. 0:沒有中斷

😃 說的啥意思啊????

/* Mode meaning to reinterrupt on exit from wait */
        private static final int REINTERRUPT =  1;
        
/* Mode meaning to throw InterruptedException on exit from wait */
        private static final int THROW_IE    = -1;

transferAfterCancelledWait

爲了回答上述問題,我們可以接着看transferAfterCancelledWait方法,首選,能進入transferAfterCancelledWait這個方法,說明前面的Thread.interrupted()的判斷爲true,表示發生了中斷,但發生中斷後,如何判斷中斷髮生在signal之前、還是之後呢?

如果是在signal之前發生的中斷,那麼肯定還沒進入同步隊列。那麼我們是否可以通過判斷同步隊列是否存在該節點來達到目的呢?

我覺得可以,不過這種方法複雜度得O(n)吧~而源碼中通過compareAndSetWaitStatus方法(CAS操作)試圖將CONDITION狀態置爲0,如果成功,說明是在signal之前發生的中斷(signal之後狀態已經被修改了)

 /**
     * Transfers node, if necessary, to sync queue after a cancelled wait.
     * Returns true if thread was cancelled before being signalled.
     *
     * @param node the node
     * @return true if cancelled before the node was signalled
     */
    final boolean transferAfterCancelledWait(Node node) {
        // 如果能通過CAS成功將等待狀態置爲0, 說明中斷在signal之前
        if (compareAndSetWaitStatus(node, Node.CONDITION, 0)) {
            // 進入到同步隊列中
            enq(node);
            return true;
        }
        /*
         * If we lost out to a signal(), then we can't proceed
         * until it finishes its enq().  Cancelling during an
         * incomplete transfer is both rare and transient, so just
         * spin.
         */
        // 執行到這裏說明上面的CAS不成功
        // 說明中斷髮生在signal之後
        while (!isOnSyncQueue(node))
            // 線程讓步
            Thread.yield();
        return false;
    }

既然CAS不成功,說明中斷髮生在signal之後,但我還是得 while (!isOnSyncQueue(node))再判斷一下,前面已經說了,signal發生時,不是立刻就到達同步隊列的,如果同步隊列沒有,Thread.yield()這裏可以理解爲先讓一下步,緩一緩,等節點到了同步隊列,我再返回。


await

transferAfterCancelledWait方法分析完畢,再回到await方法下面這一行代碼,此時interruptMode 就不會爲0了,即跳出while循環了。

if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)

此時繼續往下,執行這部分代碼,當interruptMode爲THROW_IE,表示signal之前發生的中斷,此時在中斷報告中拋出了異常。如果爲REINTERRUPT,我們在報告中進行自我中斷。這一點和我們分析acquire的時候有點兒像。

究其根本原因都是Thread.interrupted()並不會真的中斷,只是進行判斷、返回結果並清除中斷標識。真正是否中斷交由頂層接口進行操作。

 if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
     interruptMode = REINTERRUPT;
 if (node.nextWaiter != null) // clean up if cancelled
        unlinkCancelledWaiters();
  if (interruptMode != 0)
       reportInterruptAfterWait(interruptMode);
 private void reportInterruptAfterWait(int interruptMode)
            throws InterruptedException {
            if (interruptMode == THROW_IE)
                throw new InterruptedException();
            else if (interruptMode == REINTERRUPT)
                selfInterrupt();
        }

總結

本文主要分析await後掛起的線程醒來時,接下來的運行流程。由於醒來時,程序並不知道原因所以需要進行判斷。

而醒來的原因又可以分爲signal自然喚醒、中斷(被迫喚醒)。其中,中斷導致的喚醒,又可以分爲signal前中斷和signal後中斷,針對這些情況,本文都做了分析。

(完結)


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