Top


定義場所(file name)

hotspot/src/share/vm/runtime/mutex.cpp

名前(function name)

int Monitor::IWait (Thread * Self, jlong timo) {

本体部(body)

  {- -------------------------------------------
  (1) (assert)
      ---------------------------------------- -}

      assert (ILocked(), "invariant") ;

  {- -------------------------------------------
  (1) (この関数では, 以下の4つの処理を行う)
      ---------------------------------------- -}

      // Phases:
      // 1. Enqueue Self on WaitSet - currently prepend
      // 2. unlock - drop the outer lock
      // 3. wait for either notification or timeout
      // 4. lock - reentry - reacquire the outer lock

  {- -------------------------------------------
  (1) (変数宣言など)
      ---------------------------------------- -}

      ParkEvent * const ESelf = Self->_MutexEvent ;

  {- -------------------------------------------
  (1) カレントスレッドの _MutexEvent フィールド(以下の ESelf)をリセットしておく.
      (ついでに, OrderAccess::fence() でメモリバリアも張っておく.
       以降の park() 操作での load/store がこの初期化操作の store を追い抜くのは禁止.)
      ---------------------------------------- -}

      ESelf->Notified = 0 ;
      ESelf->reset() ;
      OrderAccess::fence() ;

  {- -------------------------------------------
  (1) カレントスレッドを _WaitSet に登録する.
      (正確には, カレントスレッドの _MutexEvent フィールドにある ParkEvent を _WaitSet に登録)

      なお, この処理は _WaitLock を取得して排他した状態で行う.
      (コメントによると, 理想的には Monitor の outer lock (_LockWord?) を
       握っているスレッドだけが WaitSet をいじれる, ということにしたいところだけど, 
       wait 待ちしていたスレッドがタイムアウトによって wait から外れる場合には
       ロックを持っていないのに WaitSet をいじる必要がある.
       で, WaitSet と EntryList|cxq の両方に同時に同じ ParkEvent を登録することは認めていない.
       というわけで, WaitSet に登録されている状態で outer lock を取りに行って競合することは出来ないので, 
       WaitLock という構造を用意している, とのこと.

       他の解決法としては, スレッドクラスに新しい ParkEvent ("WaitEvent") を追加するとか.
       WaitSet に登録するのは _MutexEvent ではなく WaitEvent ということにすれば, 
       同じ ParkEvent が WaitSet と EntryList|cxq の両方に登録されることはなくなる.

       あるいは, ParkEvent 内にリスト形成用のフィールドを新たに1つ追加するだけでもいいかもしれない.
       今は1つだけだが2つにして, 1つを EntryList 用, もう1つを WaitSet 用に使う.
       あるいは, ParkEvent の役割を2つに分けて, 
       純粋にイベントのみを扱う部分とリストを形成する ListElement にしてもいいかもしれない.)
      ---------------------------------------- -}

      // Add Self to WaitSet
      // Ideally only the holder of the outer lock would manipulate the WaitSet -
      // That is, the outer lock would implicitly protect the WaitSet.
      // But if a thread in wait() encounters a timeout it will need to dequeue itself
      // from the WaitSet _before it becomes the owner of the lock.  We need to dequeue
      // as the ParkEvent -- which serves as a proxy for the thread -- can't reside
      // on both the WaitSet and the EntryList|cxq at the same time..  That is, a thread
      // on the WaitSet can't be allowed to compete for the lock until it has managed to
      // unlink its ParkEvent from WaitSet.  Thus the need for WaitLock.
      // Contention on the WaitLock is minimal.
      //
      // Another viable approach would be add another ParkEvent, "WaitEvent" to the
      // thread class.  The WaitSet would be composed of WaitEvents.  Only the
      // owner of the outer lock would manipulate the WaitSet.  A thread in wait()
      // could then compete for the outer lock, and then, if necessary, unlink itself
      // from the WaitSet only after having acquired the outer lock.  More precisely,
      // there would be no WaitLock.  A thread in in wait() would enqueue its WaitEvent
      // on the WaitSet; release the outer lock; wait for either notification or timeout;
      // reacquire the inner lock; and then, if needed, unlink itself from the WaitSet.
      //
      // Alternatively, a 2nd set of list link fields in the ParkEvent might suffice.
      // One set would be for the WaitSet and one for the EntryList.
      // We could also deconstruct the ParkEvent into a "pure" event and add a
      // new immortal/TSM "ListElement" class that referred to ParkEvents.
      // In that case we could have one ListElement on the WaitSet and another
      // on the EntryList, with both referring to the same pure Event.

      Thread::muxAcquire (_WaitLock, "wait:WaitLock:Add") ;
      ESelf->ListNext = _WaitSet ;
      _WaitSet = ESelf ;
      Thread::muxRelease (_WaitLock) ;

  {- -------------------------------------------
  (1) Monitor::IUnlock() を呼んで, ロックを手放す.

      (なおコメントでは, 
       wait() から呼び出した場合, Monitor::IUnlock() 内の
       次のスレッドを起こそうとする処理の手前でブロックされる, 
       と書いてあるが, そんなコードあったっけ??? 
       以下の ParkCommon() でブロックされるんだと思っていたが...  #TODO)
      ---------------------------------------- -}

      // Release the outer lock
      // We call IUnlock (RelaxAssert=true) as a thread T1 might
      // enqueue itself on the WaitSet, call IUnlock(), drop the lock,
      // and then stall before it can attempt to wake a successor.
      // Some other thread T2 acquires the lock, and calls notify(), moving
      // T1 from the WaitSet to the cxq.  T2 then drops the lock.  T1 resumes,
      // and then finds *itself* on the cxq.  During the course of a normal
      // IUnlock() call a thread should _never find itself on the EntryList
      // or cxq, but in the case of wait() it's possible.
      // See synchronizer.cpp objectMonitor::wait().
      IUnlock (true) ;

  {- -------------------------------------------
  (1) カレントスレッドの _MutexEvent フィールド(以下の ESelf)の Notified がセットされるまで, 
      以下の for ループ内で ParkCommon() を呼び出し続けて待機する.
      ただし, ParkCommon() の返値が OS_TIMEOUT であれば, 
      Notified フィールドの値に関係なく, 待機は終了する.

      (また, NativeMonitorFlags の 1bit目(1)が立っている場合には, 
       Notified フィールドの値や ParkCommon() の返値に関係なく, 
       ParkCommon() の呼び出しは最大 1回で終了する模様.
       これはどういう最適化?? #TODO)

      (なお, Notified フィールドは Monitor::notify() 内でクリアされる.
       See: Monitor::notify())
      ---------------------------------------- -}

      // Wait for either notification or timeout
      // Beware that in some circumstances we might propagate
      // spurious wakeups back to the caller.

      for (;;) {
        if (ESelf->Notified) break ;
        int err = ParkCommon (ESelf, timo) ;
        if (err == OS_TIMEOUT || (NativeMonitorFlags & 1)) break ;
      }

  {- -------------------------------------------
  (1) タイムアウトで待機が解けた場合(= この時点で Notified フィールドが 0 の場合) には, 
      カレントスレッドの _MutexEvent フィールド(以下の ESelf)は
      まだ WaitSet に登録されたままになっているので, 
      WaitSet から ESelf を外す処理を行う.
      (タイムアウト以外の場合には, ESelf は cxq|EntryList もしくは OnDeck に移動されている)

      (なお, この WaitSet から ESelf を外す処理は _WaitLock を取得して排他した状態で行う.)
      ---------------------------------------- -}

      // Prepare for reentry - if necessary, remove ESelf from WaitSet
      // ESelf can be:
      // 1. Still on the WaitSet.  This can happen if we exited the loop by timeout.
      // 2. On the cxq or EntryList
      // 3. Not resident on cxq, EntryList or WaitSet, but in the OnDeck position.

      OrderAccess::fence() ;
      int WasOnWaitSet = 0 ;
      if (ESelf->Notified == 0) {
        Thread::muxAcquire (_WaitLock, "wait:WaitLock:remove") ;
        if (ESelf->Notified == 0) {     // DCL idiom
          assert (_OnDeck != ESelf, "invariant") ;   // can't be both OnDeck and on WaitSet
          // ESelf is resident on the WaitSet -- unlink it.
          // A doubly-linked list would be better here so we can unlink in constant-time.
          // We have to unlink before we potentially recontend as ESelf might otherwise
          // end up on the cxq|EntryList -- it can't be on two lists at once.
          ParkEvent * p = _WaitSet ;
          ParkEvent * q = NULL ;            // classic q chases p
          while (p != NULL && p != ESelf) {
            q = p ;
            p = p->ListNext ;
          }
          assert (p == ESelf, "invariant") ;
          if (p == _WaitSet) {      // found at head
            assert (q == NULL, "invariant") ;
            _WaitSet = p->ListNext ;
          } else {                  // found in interior
            assert (q->ListNext == p, "invariant") ;
            q->ListNext = p->ListNext ;
          }
          WasOnWaitSet = 1 ;        // We were *not* notified but instead encountered timeout
        }
        Thread::muxRelease (_WaitLock) ;
      }

  {- -------------------------------------------
  (1) wait の待機が終わったので, ロックの再確保を行う.
      タイムアウトで起きた場合には(= WasOnWaitSet が true の場合には), Monitor::ILock() を呼んでロックの再確保を行う.
      それ以外の場合には, (既に cxq|EntryList 又は OnDeck にいるので)
      OnDeck スレッドに選ばれるまで Monitor::TrySpin() と ParkCommon を繰り返すことでロックの再確保を行う.

      (なお, この Monitor::TrySpin() と ParkCommon を繰り返すコードは
       Monitor::ILock() 内のコードをコピペしてきたもの, とのこと)
      ---------------------------------------- -}

      // Reentry phase - reacquire the lock
      if (WasOnWaitSet) {
        // ESelf was previously on the WaitSet but we just unlinked it above
        // because of a timeout.  ESelf is not resident on any list and is not OnDeck
        assert (_OnDeck != ESelf, "invariant") ;
        ILock (Self) ;
      } else {
        // A prior notify() operation moved ESelf from the WaitSet to the cxq.
        // ESelf is now on the cxq, EntryList or at the OnDeck position.
        // The following fragment is extracted from Monitor::ILock()
        for (;;) {
          if (_OnDeck == ESelf && TrySpin(Self)) break ;
          ParkCommon (ESelf, 0) ;
        }
        assert (_OnDeck == ESelf, "invariant") ;
        _OnDeck = NULL ;
      }

  {- -------------------------------------------
  (1) (assert)
      ---------------------------------------- -}

      assert (ILocked(), "invariant") ;

  {- -------------------------------------------
  (1) 結果をリターン
      ---------------------------------------- -}

      return WasOnWaitSet != 0 ;        // return true IFF timeout
    }

This document is available under the GNU GENERAL PUBLIC LICENSE Version 2.