Top


定義場所(file name)

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

説明(description)

// ILock and IWait are the lowest level primitive internal blocking // synchronization functions. The callers of IWait and ILock must have // performed any needed state transitions beforehand. // IWait and ILock may directly call park() without any concern for thread state. // Note that ILock and IWait do not access _owner. // _owner is a higher-level logical concept.

名前(function name)

void Monitor::ILock (Thread * Self) {

本体部(body)

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

      assert (_OnDeck != Self->_MutexEvent, "invariant") ;

  {- -------------------------------------------
  (1) まずは Monitor::TryFast() で取得を試みる.
      成功すれば, ここでリターン.
      ---------------------------------------- -}

  {- -------------------------------------------
  (1) (なお, ここのリターン処理には Exeunt というラベルが付いている.
       これ以降でも, ロック処理を終了してリターンしたくなった箇所には
       "goto Exeunt" と書かれていることが多い.)
      ---------------------------------------- -}

      if (TryFast()) {
     Exeunt:
        assert (ILocked(), "invariant") ;
        return ;
      }

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

      ParkEvent * const ESelf = Self->_MutexEvent ;
      assert (_OnDeck != ESelf, "invariant") ;

  {- -------------------------------------------
  (1) Monitor::TrySpin() でスピンロックしてみて確保を試みる.
      もしこれでロックが取れたら, Exeunt ラベルに飛んでリターンする.
      ---------------------------------------- -}

      // As an optimization, spinners could conditionally try to set ONDECK to _LBIT
      // Synchronizer.cpp uses a similar optimization.
      if (TrySpin (Self)) goto Exeunt ;

  {- -------------------------------------------
  (1) (以降は, 本当に contention している場合の処理.
       しょうが無いので, カレントスレッドを cxq に追加して待機するか, もしくは ...#TODO

       なお, LockWord の中身は (cxq,LOCKBYTE) となっている.)
      ---------------------------------------- -}

      // Slow-path - the lock is contended.
      // Either Enqueue Self on cxq or acquire the outer lock.
      // LockWord encoding = (cxq,LOCKBYTE)

  {- -------------------------------------------
  (1) os::PlatformEvent::reset() を呼んで, 
      カレントスレッドの _MutexEvent フィールド (以下の ESelf) をリセットしておく.

      (ついでに, OrderAccess::fence() でメモリバリアも張っておく.
       以降の park() 操作での load/store がこの初期化操作の store を追い抜くのは禁止.)
      ---------------------------------------- -}

      ESelf->reset() ;
      OrderAccess::fence() ;

  {- -------------------------------------------
  (1) とりあえず, いきなり CASPTR で _OnDeck フィールドに自分自身をねじ込んでみる.
      成功したら, (以下の ParkCommon() による待機処理はすっ飛ばして) 
      一気に OnDeck_LOOP ラベルまでジャンプする.
      (失敗したら, このまま下の処理にフォールスルー)

      (なお, この最適化は NativeMonitorFlags の 6bit目(32)が立っている場合にのみ行われる)
      ---------------------------------------- -}

      // Optional optimization ... try barging on the inner lock
      if ((NativeMonitorFlags & 32) && CASPTR (&_OnDeck, NULL, UNS(Self)) == 0) {
        goto OnDeck_LOOP ;
      }

  {- -------------------------------------------
  (1) Monitor::AcquireOrPush() で, ロックの取得または待ち行列への自分自身の追加を行う.
      もしこれでロックが取れたら, Exeunt ラベルに飛んでリターンする.
      逆にロックが取れなかったら, ParkCommon() を呼んで誰かが起こしてくれるまで待機する.
      (より正確には, _OnDeck フィールドが自分の _MutexEvent フィールド(以下の ESelf) を指すようになるまで待機し続ける.
       もし ParkCommon() から目覚めても, _OnDeck フィールドが自分を指していない場合には, 
       指すようになるまで何度でも ParkCommon() を呼んで待機する)
      ---------------------------------------- -}

      if (AcquireOrPush (ESelf)) goto Exeunt ;

      // At any given time there is at most one ondeck thread.
      // ondeck implies not resident on cxq and not resident on EntryList
      // Only the OnDeck thread can try to acquire -- contended for -- the lock.
      // CONSIDER: use Self->OnDeck instead of m->OnDeck.
      // Deschedule Self so that others may run.
      while (_OnDeck != ESelf) {
        ParkCommon (ESelf, 0) ;
      }

  {- -------------------------------------------
  (1) Monitor::TrySpin() でロックを確保する.
      (なお, ロックの取得に失敗したら, 
       一旦 ParkCommon() で眠りについた後, 再度 Monitor::TrySpin() を試みる.
       以後, Monitor::TrySpin() が成功するまで繰り返し)

       <= ロックの取得に失敗するケースとは, unlock 直後に新しいスレッドが来てそちらに取られてしまったケース??
      ---------------------------------------- -}

      // Self is now in the ONDECK position and will remain so until it
      // manages to acquire the lock.
     OnDeck_LOOP:
      for (;;) {
        assert (_OnDeck == ESelf, "invariant") ;
        if (TrySpin (Self)) break ;
        // CONSIDER: if ESelf->TryPark() && TryLock() break ...
        // It's probably wise to spin only if we *actually* blocked
        // CONSIDER: check the lockbyte, if it remains set then
        // preemptively drain the cxq into the EntryList.
        // The best place and time to perform queue operations -- lock metadata --
        // is _before having acquired the outer lock, while waiting for the lock to drop.
        ParkCommon (ESelf, 0) ;
      }

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

      assert (_OnDeck == ESelf, "invariant") ;

  {- -------------------------------------------
  (1) 自分のロック処理は終わったので, _OnDeck フィールドをクリアしておく
      ---------------------------------------- -}

      _OnDeck = NULL ;

  {- -------------------------------------------
  (1) (なお, 今のところ inner lock (OnDeck) は
       outer lock が取れたらすぐに解放している(クリアしている)けど, 
       以下のような最適化を行うことも出来る.
       A. IUnlock() の時まで OnDeck を保持し続ける (こうすると IUnlock() 内で OnDeck を取得する必要が生じた時に速くなるかも).
       B. OnDeck を保持している間に, 次のスレッドを EntryList から選び OnDeck に設定する (見つからなければ OnDeck はクリアする).
          なお, この場合は選ぶ操作が (outer lock を保持した) critical section 内で行われることになるので, 
          この操作に時間が掛からないよう注意が必要.)
      ---------------------------------------- -}

      // Note that we current drop the inner lock (clear OnDeck) in the slow-path
      // epilog immediately after having acquired the outer lock.
      // But instead we could consider the following optimizations:
      // A. Shift or defer dropping the inner lock until the subsequent IUnlock() operation.
      //    This might avoid potential reacquisition of the inner lock in IUlock().
      // B. While still holding the inner lock, attempt to opportunistically select
      //    and unlink the next ONDECK thread from the EntryList.
      //    If successful, set ONDECK to refer to that thread, otherwise clear ONDECK.
      //    It's critical that the select-and-unlink operation run in constant-time as
      //    it executes when holding the outer lock and may artificially increase the
      //    effective length of the critical section.
      // Note that (A) and (B) are tantamount to succession by direct handoff for
      // the inner lock.

  {- -------------------------------------------
  (1) Exeunt ラベルに飛んでリターンする.
      ---------------------------------------- -}

      goto Exeunt ;
    }

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