Top


定義場所(file name)

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

説明(description)

(コメントによると, ObjectMonitor::wait() のコードを修正した場合には その修正を ObjectMonitor::complete_exit() にも反映させること, とのこと)

// -----------------------------------------------------------------------------
// Wait/Notify/NotifyAll
//
// Note: a subset of changes to ObjectMonitor::wait()
// will need to be replicated in complete_exit above

名前(function name)

void ObjectMonitor::wait(jlong millis, bool interruptible, TRAPS) {

本体部(body)

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

       Thread * const Self = THREAD ;
       assert(Self->is_Java_thread(), "Must be Java thread!");
       JavaThread *jt = (JavaThread *)THREAD;

  {- -------------------------------------------
  (1) ObjectMonitor::DeferredInitialize() を呼んで, 
      (まだ初期化が終わっていなければ) Knob_* 変数の値の初期化を行う.
      ---------------------------------------- -}

       DeferredInitialize () ;

  {- -------------------------------------------
  (1) (ロックを持っているスレッドしか wait() を呼んではいけないので)
      CHECK_OWNER() でロックを持っているかどうかをチェックしておく.
      持っていなければ IllegalMonitorStateException.

      (なお, もしカレントスレッドがロックを持っているにも関わらず
       ObjectMonitor 内の _owner フィールドがカレントスレッドを指していない場合, 
      _owner をカレントスレッドに変更してもいる)
      ---------------------------------------- -}

       // Throw IMSX or IEX.
       CHECK_OWNER();

  {- -------------------------------------------
  (1) もしカレントスレッドに対して java.lang.Thread.interrupt() が呼ばれていたら, 
      wait 処理はここで打ち切って, 代わりに InterruptedException を出す.

      (なお, InterruptedException を出す場合には, (JVMTI のフック点)の処理も行われる)
      ---------------------------------------- -}

       // check for a pending interrupt
       if (interruptible && Thread::is_interrupted(Self, true) && !HAS_PENDING_EXCEPTION) {
         // post monitor waited event.  Note that this is past-tense, we are done waiting.
         if (JvmtiExport::should_post_monitor_waited()) {
            // Note: 'false' parameter is passed here because the
            // wait was not timed out due to thread interrupt.
            JvmtiExport::post_monitor_waited(jt, this, false);
         }
         TEVENT (Wait - Throw IEX) ;
         THROW(vmSymbols::java_lang_InterruptedException());
         return ;
       }

  {- -------------------------------------------
  (1) (トレース出力)
      ---------------------------------------- -}

       TEVENT (Wait) ;

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

       assert (Self->_Stalled == 0, "invariant") ;

  {- -------------------------------------------
  (1) カレントスレッドの _Stalled フィールドに値をセットしておく.
      (これは, Adaptive Spinnning 中に使用される模様.
       See: ObjectMonitor::NotRunnable())
      ---------------------------------------- -}

       Self->_Stalled = intptr_t(this) ;

  {- -------------------------------------------
  (1) これから wait 処理に入るので, 
      JavaThread::set_current_waiting_monitor() を呼んで
      wait 対象の ObjectMonitor オブジェクトを登録しておく.
      (この情報は JVMTI や JMM から使われる模様.
       See: JavaThread::current_waiting_monitor())
      ---------------------------------------- -}

       jt->set_current_waiting_monitor(this);

  {- -------------------------------------------
  (1) 待ちキューに登録するための ObjectWaiter を作成し, そのフィールドを初期化しておく.
      ---------------------------------------- -}

       // create a node to be put into the queue
       // Critically, after we reset() the event but prior to park(), we must check
       // for a pending interrupt.
       ObjectWaiter node(Self);
       node.TState = ObjectWaiter::TS_WAIT ;

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

       Self->_ParkEvent->reset() ;

  {- -------------------------------------------
  (1) OrderAccess::fence() で, ここにメモリバリアを張っておく.
      (_ParkEvent への書き込みと interrupted flag の確認を順序づける必要があるため)
      ---------------------------------------- -}

       OrderAccess::fence();          // ST into Event; membar ; LD interrupted-flag

  {- -------------------------------------------
  (1) ObjectMonitor::AddWaiter() を呼んで, 
      さっき作った ObjectWaiter を待ちキュー(_WaitSet)の末尾に追加する.

      (なお, この処理は _WaitSetLock を取得して排他した状態で行う)

      (なおコメントによると, 現在は _WaitSetLock は circular doubly linked list (CDDL) だが, 
       別に優先度付きキューでもいいし他のデータ構造でもいい, とのこと)

      (また, 理想的にはこの ObjectMonitor のロックを握っているスレッドだけが 
       _WaitSet を操作できる, ということにしたいところだが, 
       wait 待ちしていたスレッドがタイムアウトによって wait から外れる場合には
       ロックを持っていないのに WaitSet をいじる必要がある.
       というわけで, _WaitSetLock というロックで排他を取っている.
       なお, 競合することは極めてまれなので単純なスピンロックを用いている, 
       とのこと)
      ---------------------------------------- -}

       // Enter the waiting queue, which is a circular doubly linked list in this case
       // but it could be a priority queue or any data structure.
       // _WaitSetLock protects the wait queue.  Normally the wait queue is accessed only
       // by the the owner of the monitor *except* in the case where park()
       // returns because of a timeout of interrupt.  Contention is exceptionally rare
       // so we use a simple spin-lock instead of a heavier-weight blocking lock.

       Thread::SpinAcquire (&_WaitSetLock, "WaitSet - add") ;
       AddWaiter (&node) ;
       Thread::SpinRelease (&_WaitSetLock) ;

  {- -------------------------------------------
  (1) この ObjectMonitor オブジェクトの _Responsible を NULL に戻しておく. (See: 1-0 model)

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

       if ((SyncFlags & 4) == 0) {
          _Responsible = NULL ;
       }

  {- -------------------------------------------
  (1) ロックを一時的に手放すので, _recursions は 0 に戻しておく.
      (現在の _recursions の値は, notify() で起こされた後のために, save という局所変数に保存しておく).
      ついでに, _waiters(待ちキューの長さを示すフィールド) もインクリメントしておく.
      ---------------------------------------- -}

       intptr_t save = _recursions; // record the old recursion count
       _waiters++;                  // increment the number of waiters
       _recursions = 0;             // set the recursion level to be 1

  {- -------------------------------------------
  (1) ObjectMonitor::exit() でロックを解放する.
      ---------------------------------------- -}

       exit (Self) ;                    // exit the monitor

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

       guarantee (_owner != Self, "invariant") ;

  {- -------------------------------------------
  (1) (ObjectMonitor::exit() でロックを解放して以降は, 他のスレッドがロックを確保可能な状態になる.
       そのため, この時点で他スレッドによるロックの確保(enter)/解放処理(exit)が行われているかもしれない.
       その結果として, この時点で既に自分が notify() され, 次のスレッドに選ばれている可能性もある.)

      既に自分が次のスレッドに選ばれていたら, 自分に対して unpark() しておく.
      ---------------------------------------- -}

       // As soon as the ObjectMonitor's ownership is dropped in the exit()
       // call above, another thread can enter() the ObjectMonitor, do the
       // notify(), and exit() the ObjectMonitor. If the other thread's
       // exit() call chooses this thread as the successor and the unpark()
       // call happens to occur while this thread is posting a
       // MONITOR_CONTENDED_EXIT event, then we run the risk of the event
       // handler using RawMonitors and consuming the unpark().
       //
       // To avoid the problem, we re-post the event. This does no harm
       // even if the original unpark() was not consumed because we are the
       // chosen successor for this monitor.
       if (node._notified != 0 && _succ == Self) {
          node._event->unpark();
       }

  {- -------------------------------------------
  (1) (スレッドは待ちキュー(WaitSet)に登録されたので, この後のコードでいよいよ park() による待機に入る.)

      (なおコメントによると, 
       「マルチプロセッサ環境なら, park() する前にちょっとスピンしてみるのもいいかもしれない.
         後, コード自体は以下のような形にリファクタリングしたい.
           while (!timeout && !interrupted && _notified == 0) park()」
       とのこと)
      ---------------------------------------- -}

       // The thread is on the WaitSet list - now park() it.
       // On MP systems it's conceivable that a brief spin before we park
       // could be profitable.
       //
       // TODO-FIXME: change the following logic to a loop of the form
       //   while (!timeout && !interrupted && _notified == 0) park()

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

       int ret = OS_OK ;
       int WasNotified = 0 ;

  {- -------------------------------------------
  (1) OSThreadWaitState で OSThread の状態を変更しておく.
      ---------------------------------------- -}

       { // State transition wrappers
         OSThread* osthread = Self->osthread();
         OSThreadWaitState osts(osthread, true);

  {- -------------------------------------------
  (1) ThreadBlockInVM で JavaThread の状態を変更した後, 
      カレントスレッドの _ParkEvent フィールドに対して os::PlatformEvent::park() を呼ぶことで眠りにつく.
      (引数のタイムアウト時間(以下の millis)に応じて, 呼び出す park() が少し変わる 
       タイムアウト時間の指定がない場合 (millis が 0 以下の場合) には, 
       タイムアウト無しの os::PlatformEvent::park() を呼び出す.
       そうでなければ, タイムアウト付きの os::PlatformEvent::park() を呼び出す.)


      (なお, この wait 呼び出しが割り込み可能な場合(= 引数の interrupted が true な場合), 
       既に java.lang.Thread.interrupt() が呼ばれていたら, 寝る必要は無い.
       このため, 寝る直前に pending interrupt があるかどうかを調べ, 
       ある場合には os::PlatformEvent::park() を呼び出さないようにしている.)

      (また, この時点で WaitSet への登録は終わっているため, 
       既に notify() が呼び出されている(= _notified が 1 になっている)可能性もある.
       このため, 寝る直前に _notified の値を調べ, 
       0 でなければ os::PlatformEvent::park() を呼び出さないようにしている.)

      (また, 眠っている間に suspend された場合のために, 
       os::PlatformEvent::park() から返ってきた後で 
       ObjectMonitor::ExitSuspendEquivalent() による確認を行っている.

       suspend されていた場合は, JavaThread::java_suspend_self() を呼んで
       サスペンドが解除されるまで再び眠りにつく.

       なおコメントによると, このケースについては以下のコードを追加する必要がある, とのことだが...
         if succ == Self then succ = null)


      #TODO  JavaThread::set_suspend_equivalent() はどういう意味がある??
      ---------------------------------------- -}

         {
           ThreadBlockInVM tbivm(jt);
           // Thread is in thread_blocked state and oop access is unsafe.
           jt->set_suspend_equivalent();

           if (interruptible && (Thread::is_interrupted(THREAD, false) || HAS_PENDING_EXCEPTION)) {
               // Intentionally empty
           } else
           if (node._notified == 0) {
             if (millis <= 0) {
                Self->_ParkEvent->park () ;
             } else {
                ret = Self->_ParkEvent->park (millis) ;
             }
           }

           // were we externally suspended while we were waiting?
           if (ExitSuspendEquivalent (jt)) {
              // TODO-FIXME: add -- if succ == Self then succ = null.
              jt->java_suspend_self();
           }

  {- -------------------------------------------
  (1) (ThreadBlockInVM によって状態が変わっているのはここまで)
      ---------------------------------------- -}

         } // Exit thread safepoint: transition _thread_blocked -> _thread_in_vm


  {- -------------------------------------------
  (1) (この時点では, カレントスレッドはまだ WaitSet にいるかもしれないし, EntryList や cxq に移動されているかもしれない)
      まだ WaitSet にいる場合には, ObjectMonitor::DequeueSpecificWaiter() を呼んで 
      WaitSet から外す処理を行う.

      (これは notify() 以外の原因による起床 (timeout, interrupt). 
      このため _notified フィールドは 0 のままになっているはず)

      (なお, _WaitSet をいじるので, この処理は _WaitSetLock を取得して排他した状態で行う.
       また, DCL(Double Checked Locking) のイディオムにのっとって, 
       _WaitSetLock を取得した後に, 再度 _WaitSet にいることを確認している.)

      (なお, DCL の一回目の TState チェックの前にメモリバリアを張る必要は無い.
       この時点で古い値が見えたとしても, ロックを取ってから行う二回目のチェック時には新しい値が見えるので.)
      ---------------------------------------- -}

         // Node may be on the WaitSet, the EntryList (or cxq), or in transition
         // from the WaitSet to the EntryList.
         // See if we need to remove Node from the WaitSet.
         // We use double-checked locking to avoid grabbing _WaitSetLock
         // if the thread is not on the wait queue.
         //
         // Note that we don't need a fence before the fetch of TState.
         // In the worst case we'll fetch a old-stale value of TS_WAIT previously
         // written by the is thread. (perhaps the fetch might even be satisfied
         // by a look-aside into the processor's own store buffer, although given
         // the length of the code path between the prior ST and this load that's
         // highly unlikely).  If the following LD fetches a stale TS_WAIT value
         // then we'll acquire the lock and then re-fetch a fresh TState value.
         // That is, we fail toward safety.

         if (node.TState == ObjectWaiter::TS_WAIT) {
             Thread::SpinAcquire (&_WaitSetLock, "WaitSet - unlink") ;
             if (node.TState == ObjectWaiter::TS_WAIT) {
                DequeueSpecificWaiter (&node) ;       // unlink from WaitSet
                assert(node._notified == 0, "invariant");
                node.TState = ObjectWaiter::TS_RUN ;
             }
             Thread::SpinRelease (&_WaitSetLock) ;
         }

  {- -------------------------------------------
  (1) (assert)
      (この時点で, カレントスレッドは以下のどれかの状態にある. (See: ObjectMonitor::notify(), ObjectMonitor::notifyAll())
       * どちらの待ち行列上にもいない (TS_RUN)
       * EntryList に移動されている (TS_ENTER)
       * cxq に移動されている (TS_CXQ)

       なお, カレントスレッドから見た場合には TState は値が急に変わることはない.
       他のスレッドが非同期的に TState を変更することはないので.)
      ---------------------------------------- -}

         // The thread is now either on off-list (TS_RUN),
         // on the EntryList (TS_ENTER), or on the cxq (TS_CXQ).
         // The Node's TState variable is stable from the perspective of this thread.
         // No other threads will asynchronously modify TState.
         guarantee (node.TState != ObjectWaiter::TS_WAIT, "invariant") ;

  {- -------------------------------------------
  (1) _succ が自分を指していたら, NULL に戻しておく.
      また, notify() が呼び出されたかどうか(= _notified の値)は, この時点で確認している
      (確認結果は後で使用する)

      (これらの値(_succ, _notified)の確認処理が古い値を見てしまわないよう, 
       OrderAccess::loadload() でメモリバリアも張っている.

       <= 順序付けが OrderAccess::loadload() なのは, 
          notify() や notifyAll() の処理では
          TState と _notified が (_WaitSetLock で保護された) 同じ critical section 内で変更され, 
          _succ がそれと同時かそれ以降に変更されることになるので, 
          上の最新の TState を見る処理を追い抜かなければ他の値も問題ない, ということだと思われる.

          タイムアウトや interrupt 時については, 上の WaitSet から外す処理を行う際に
          _WaitSetLock を取ってシリアライズするので, 
          wait() 側が早ければ notify() 側での変更処理は行われないし, 
          notify() 側が早ければ上のケースと同じ話になる)
      ---------------------------------------- -}

         OrderAccess::loadload() ;
         if (_succ == Self) _succ = NULL ;
         WasNotified = node._notified ;

  {- -------------------------------------------
  (1) (wait による待機が終わったので, 以降でロックを取り直す処理を行う.
       実際にロックが取得できるまで, OSThread の状態は OBJECT_WAIT のままにしておく.
       JavaThread の方は thread_in_vm にしているので, これ以降で oop にアクセスするのは問題ない.
       ただし, JavaThread の方も一度状態変更したため, Safepoint が発生している可能性はある.
       そのため, それ以前に取得した oop の生のアドレスは変わっているかもしれないことに注意.)
      ---------------------------------------- -}

         // Reentry phase -- reacquire the monitor.
         // re-enter contended monitor after object.wait().
         // retain OBJECT_WAIT state until re-enter successfully completes
         // Thread state is thread_in_vm and oop access is again safe,
         // although the raw address of the object may have changed.
         // (Don't cache naked oops over safepoints, of course).

  {- -------------------------------------------
  (1) (JVMTI のフック点)
      ---------------------------------------- -}

         // post monitor waited event. Note that this is past-tense, we are done waiting.
         if (JvmtiExport::should_post_monitor_waited()) {
           JvmtiExport::post_monitor_waited(jt, this, ret == OS_TIMEOUT);
         }

  {- -------------------------------------------
  (1) (これは何のための fence かというと...?? #TODO)

      (_succ を NULL に戻した場合には, 
       眠りにつく前に _owner の確保を試みておかないといけない, という内部規則があるので
       その順序を保証するため??)
      ---------------------------------------- -}

         OrderAccess::fence() ;

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

         assert (Self->_Stalled != 0, "invariant") ;

  {- -------------------------------------------
  (1) カレントスレッドの _Stalled フィールドに値をセットしておく.
      (これは, Adaptive Spinnning 中に使用される模様.
       See: ObjectMonitor::NotRunnable())
      ---------------------------------------- -}

         Self->_Stalled = 0 ;

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

         assert (_owner != Self, "invariant") ;

  {- -------------------------------------------
  (1) ロックをもう一度取得する.
      なお, カレントスレッドのロック状態に応じて, 以下の2通りのパスがある.
      * どちらの待ち行列上にもいない (TS_RUN)
        新しくロックを取りに来た状態(= まだ待ち行列に登録されていない状態)と同じなので, 
        ObjectMonitor::enter() を呼び出して, 改めてロック確保処理を全部やり直す.
      * EntryList に移動されている (TS_ENTER), または cxq に移動されている (TS_CXQ)
        既に待ち行列への登録処理が終わっている状態なので, ロック確保処理を全部やり直す必要は無い.
        ObjectMonitor::ReenterI() を呼んでロックの確保を行う.
        (ReenterI() は EnterI() の後半部分をコピーしたもの)

      (なお, TS_ENTER や TS_CXQ のケースのパスで呼び出される ObjectWaiter::wait_reenter_end() は, 
       (プロファイル情報の記録)を行う関数.
       (See: JavaThreadBlockedOnMonitorEnterState)
       (See: [here](no21146np.html) for details))
      ---------------------------------------- -}

         ObjectWaiter::TStates v = node.TState ;
         if (v == ObjectWaiter::TS_RUN) {
             enter (Self) ;
         } else {
             guarantee (v == ObjectWaiter::TS_ENTER || v == ObjectWaiter::TS_CXQ, "invariant") ;
             ReenterI (Self, &node) ;
             node.wait_reenter_end(this);
         }

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

         // Self has reacquired the lock.
         // Lifecycle - the node representing Self must not appear on any queues.
         // Node is about to go out-of-scope, but even if it were immortal we wouldn't
         // want residual elements associated with this thread left on any lists.
         guarantee (node.TState == ObjectWaiter::TS_RUN, "invariant") ;
         assert    (_owner == Self, "invariant") ;
         assert    (_succ != Self , "invariant") ;

  {- -------------------------------------------
  (1) (OSThreadWaitState によって状態が変更されているのはここまで)
      ---------------------------------------- -}

       } // OSThreadWaitState()

  {- -------------------------------------------
  (1) wait 処理が終わったので, 
      JavaThread::set_current_waiting_monitor() を呼んで
      登録した ObjectMonitor オブジェクトをクリアしておく.
      (この情報は JVMTI や JMM から使われる模様.
       See: JavaThread::current_waiting_monitor())
      ---------------------------------------- -}

       jt->set_current_waiting_monitor(NULL);

  {- -------------------------------------------
  (1) ObjectMonitor 内の値を wait() 前の状態に戻す (_recursions, 及び waiters フィールド).
      ---------------------------------------- -}

       guarantee (_recursions == 0, "invariant") ;
       _recursions = save;     // restore the old recursion count
       _waiters--;             // decrement the number of waiters

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

       // Verify a few postconditions
       assert (_owner == Self       , "invariant") ;
       assert (_succ  != Self       , "invariant") ;
       assert (((oop)(object()))->mark() == markOopDesc::encode(this), "invariant") ;

  {- -------------------------------------------
  (1) #TODO
      ---------------------------------------- -}

       if (SyncFlags & 32) {
          OrderAccess::fence() ;
       }

  {- -------------------------------------------
  (1) java.lang.Thread.interrupt() によって wait から起きていた場合には, 
      ここで InterruptedException を出す.

      (なお以下のコメントにある通り, 現状の実装では
       微妙なケースは以下のように扱われる.
       * Spurious wake up した場合 (notified も 0, interrupt もされていない)
         タイムアウトだということになる.
       * interrupt と notify が同時に来た場合
         notify が優先される (InterruptedException は出さない)
       * interrupt とタイムアウトが同時に来た場合
         interrupt が優先される. (InterruptedException を出す))
      ---------------------------------------- -}

       // check if the notification happened
       if (!WasNotified) {
         // no, it could be timeout or Thread.interrupt() or both
         // check for interrupt event, otherwise it is timeout
         if (interruptible && Thread::is_interrupted(Self, true) && !HAS_PENDING_EXCEPTION) {
           TEVENT (Wait - throw IEX from epilog) ;
           THROW(vmSymbols::java_lang_InterruptedException());
         }
       }

       // NOTE: Spurious wake up will be consider as timeout.
       // Monitor notify has precedence over thread interrupt.
    }

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