synchronized 中的同步佇列與等待佇列

minororange發表於2021-11-19

說明

  • 同步佇列:排隊取鎖的執行緒所在的佇列
  • 等待佇列:呼叫 wait 方法後,執行緒會從同步佇列轉移到等待佇列

synchronized 中同步佇列有兩個 _cxqEntryList,基於不同的 QMode 來調整執行緒的出隊策略

  • _cxq(競爭佇列):搶鎖失敗後,執行緒會進入此佇列,此佇列大部分情況時單向連結串列,入隊策略是 後來者當頭

  • EntryList:預設情況下(根據 Knob_MoveNotifyee 判斷,原始碼預設為 2 ,當 EntryList 不為空,Policy == 2 時,參閱 原始碼 1720-1735行),執行緒被喚醒時,會從等待佇列轉移到此佇列,此佇列是一個雙向連結串列

  • WaitSet:等待佇列,呼叫 wait 方法後,執行緒會進入此佇列

出隊策略

QMode 一共有5種值,0、1、2、3、4,不同的 QMode ,會影響 _cxq 和 EntryList 的優先順序,預設情況下,QMode 為 0

      // 當 _cxq 不為空,且 QMode 為 2 時,會直接使用 _cxq 的順序進行喚醒(後來者當頭)
      if (QMode == 2 && _cxq != NULL) {
          w = _cxq ;
          assert (w != NULL, "invariant") ;
          assert (w->TState == ObjectWaiter::TS_CXQ, "Invariant") ;
          ExitEpilog (Self, w) ;
          return ;
      }

      // 當 _cxq 不為空,且 QMode 為 3 時,將 _cqx 連結到 EntyList 後面
      if (QMode == 3 && _cxq != NULL) {
          w = _cxq ;

          ObjectWaiter * q = NULL ;
          ObjectWaiter * p ;
          // 將 _cqx 變成雙向連結串列
          for (p = w ; p != NULL ; p = p->_next) {
              guarantee (p->TState == ObjectWaiter::TS_CXQ, "Invariant") ;
              p->TState = ObjectWaiter::TS_ENTER ;
              p->_prev = q ;
              q = p ;
          }
          // 將 _cqx 拼在 EntryList 後面
          // Append the RATs to the EntryList
          // TODO: organize EntryList as a CDLL so we can locate the tail in constant-time.
          ObjectWaiter * Tail ;
          for (Tail = _EntryList ; Tail != NULL && Tail->_next != NULL ; Tail = Tail->_next) ;
          if (Tail == NULL) {
              _EntryList = w ;
          } else {
              Tail->_next = w ;
              w->_prev = Tail ;
          }

          // Fall thru into code that tries to wake a successor from EntryList
      }

      // 當 _cxq 不為空,且 QMode 為 4 時,將 EntyList 連結到 _cqx 後面
      if (QMode == 4 && _cxq != NULL) {

          w = _cxq ;
          ...
          // 將 cqx 變為雙向連結串列
          ObjectWaiter * q = NULL ;
          ObjectWaiter * p ;
          for (p = w ; p != NULL ; p = p->_next) {
              guarantee (p->TState == ObjectWaiter::TS_CXQ, "Invariant") ;
              p->TState = ObjectWaiter::TS_ENTER ;
              p->_prev = q ;
              q = p ;
          }

          // 將 EntryList 的頭設為 _cqx
          if (_EntryList != NULL) {
              q->_next = _EntryList ;
              _EntryList->_prev = q ;
          }
          _EntryList = w ;
      }

      w = _EntryList  ;
      // 除了 (Qmode == 2 && _cqx != null) 的情況,按照 EntryList 的順序出隊
      if (w != NULL) {
          assert (w->TState == ObjectWaiter::TS_ENTER, "invariant") ;
          ExitEpilog (Self, w) ;
          return ;
      }


      w = _cxq ;
      if (w == NULL) continue ;

     // EntryList 為空,QMode 為 1 ,翻轉 _cqx ,然後出隊 
      if (QMode == 1) {
         ...
         ObjectWaiter * t = w ;
         ...
         // 翻轉 _cqx
         while (t != NULL) {
             guarantee (t->TState == ObjectWaiter::TS_CXQ, "invariant") ;
             t->TState = ObjectWaiter::TS_ENTER ;
             u = t->_next ;
             t->_prev = u ;
             t->_next = s ;
             s = t;
             t = u ;
         }
         // 賦值給 _EntryList
         _EntryList  = s ;
         assert (s != NULL, "invariant") ;
      } else {
         // EntryList 為空,QMode 為 0,直接將 _cqx 賦值給 EntryList,同時變成雙向連結串列 
         _EntryList = w ;
         ObjectWaiter * q = NULL ;
         ObjectWaiter * p ;
         for (p = w ; p != NULL ; p = p->_next) {
             guarantee (p->TState == ObjectWaiter::TS_CXQ, "Invariant") ;
             p->TState = ObjectWaiter::TS_ENTER ;
             p->_prev = q ;
             q = p ;
         }
      }

      // 出隊
      w = _EntryList  ;
      if (w != NULL) {
          guarantee (w->TState == ObjectWaiter::TS_ENTER, "invariant") ;
          ExitEpilog (Self, w) ;
          return ;
      }
void ObjectMonitor::ExitEpilog (Thread * Self, ObjectWaiter * Wakee) {
   assert (_owner == Self, "invariant") ;

   // Exit protocol:
   // 1. ST _succ = wakee
   // 2. membar #loadstore|#storestore;
   // 2. ST _owner = NULL
   // 3. unpark(wakee)

   _succ = Knob_SuccEnabled ? Wakee->_thread : NULL ;
   ParkEvent * Trigger = Wakee->_event ;

   // Hygiene -- once we've set _owner = NULL we can't safely dereference Wakee again.
   // The thread associated with Wakee may have grabbed the lock and "Wakee" may be
   // out-of-scope (non-extant).
   Wakee  = NULL ;

   // Drop the lock
   OrderAccess::release_store_ptr (&_owner, NULL) ;
   OrderAccess::fence() ;                               // ST _owner vs LD in unpark()

   if (SafepointSynchronize::do_call_back()) {
      TEVENT (unpark before SAFEPOINT) ;
   }

   DTRACE_MONITOR_PROBE(contended__exit, this, object(), Self);
   Trigger->unpark() ;

   // Maintain stats and report events to JVMTI
   if (ObjectMonitor::_sync_Parks != NULL) {
      ObjectMonitor::_sync_Parks->inc() ;
   }
}

參考資料

  1. Java 併發——基石篇(中)
  2. 再談阻塞(3):cxq、EntryList與WaitSet
  3. OpenJDK / jdk8 / jdk8 / hotspot
本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章