Top


定義場所(file name)

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

名前(function name)

void Monitor::IUnlock (bool RelaxAssert) {

本体部(body)

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

      assert (ILocked(), "invariant") ;

  {- -------------------------------------------
  (1) Monitor のロックを解放する.
      (より具体的には, _LockWord フィールドの _LBIT ビットをクリアする.
       ついでに, OrderAccess::storeload() でメモリバリアを張って, 
       以降の OnDeck や cxq 等の読み取りに抜かれないようにしている.)
      ---------------------------------------- -}

      _LockWord.Bytes[_LSBINDEX] = 0 ;       // drop outer lock
      OrderAccess::storeload ();

  {- -------------------------------------------
  (1) (変数宣言など) (w はこの時点での _OnDeck の値. volatile なので最適化はされない)
      ---------------------------------------- -}

      ParkEvent * const w = _OnDeck ;

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

      assert (RelaxAssert || w != Thread::current()->_MutexEvent, "invariant") ;

  {- -------------------------------------------
  (1) もし _OnDeck フィールド(以下の w)が NULL でなければ, 2つの可能性が考えられる.
      それぞれ以下のように対処する. どちらのケースであっても, ここでリターン.
      * OnDeck スレッドが既に決まっている  (_OnDeck フィールドの _LBIT が立っていない場合)
        そのスレッドを os::PlatformEvent::unpark() で起床させて, リターン.
      * 他のスレッドが Monitor::IUnlock() 処理中で, 一時的にロックしているだけ  (_OnDeck フィールドの _LBIT が立っている場合)
        (後のことはそのスレッドに任せればいいので) 何もせずにリターン

      なお, unpark() を間違ってたくさん呼んでしまってもペナルティは少ない. #TODO
      ---------------------------------------- -}

      if (w != NULL) {
        // Either we have a valid ondeck thread or ondeck is transiently "locked"
        // by some exiting thread as it arranges for succession.  The LSBit of
        // OnDeck allows us to discriminate two cases.  If the latter, the
        // responsibility for progress and succession lies with that other thread.
        // For good performance, we also depend on the fact that redundant unpark()
        // operations are cheap.  That is, repeated Unpark()ing of the ONDECK thread
        // is inexpensive.  This approach provides implicit futile wakeup throttling.
        // Note that the referent "w" might be stale with respect to the lock.
        // In that case the following unpark() is harmless and the worst that'll happen
        // is a spurious return from a park() operation.  Critically, if "w" _is stale,
        // then progress is known to have occurred as that means the thread associated
        // with "w" acquired the lock.  In that case this thread need take no further
        // action to guarantee progress.
        if ((UNS(w) & _LBIT) == 0) w->unpark() ;
        return ;
      }

  {- -------------------------------------------
  (1) (変数宣言など) (cxq はこの時点での _LockWord の値. volatile なので最適化はされない)
      ---------------------------------------- -}

      intptr_t cxq = _LockWord.FullWord ;

  {- -------------------------------------------
  (1) もし cxq も EntryList も空であれば, することはない. 
      ここでリターン.
      ---------------------------------------- -}

      if (((cxq & ~_LBIT)|UNS(_EntryList)) == 0) {
        return ;      // normal fast-path exit - cxq and EntryList both empty
      }

  {- -------------------------------------------
  (1) もし他のスレッドがもう cxq のロックを取っていたら, 
      引き継ぎ作業を行う責任はそのスレッドにあるということなので, 何もしなくていい.
      ここでリターン.
      ---------------------------------------- -}

      if (cxq & _LBIT) {
        // Optional optimization ...
        // Some other thread acquired the lock in the window since this
        // thread released it.  Succession is now that thread's responsibility.
        return ;
      }

  {- -------------------------------------------
  (1) (なお, ここの地点に Succession というラベルが付いている. 
       Punt ラベル(後述)の処理から戻ってきた場合は, これ以降の処理を全てやり直す.)
      ---------------------------------------- -}

     Succession:

  {- -------------------------------------------
  (1) CASPTR で _OnDeck フィールドを _LBIT だけが立った状態にし, 
      カレントスレッドが引き継ぎ処理を行うことを宣言する.

      もし CASPTR が失敗したら, 引き継ぎ作業を行う責任は
      だれか他のスレッドにあるということなので, 
      ここでリターン.

      (以下のコメントには, OnDeck をロックとして使っていることなど, 
       上のコメント箇所にも書いてあった内容がもう一度書かれている.)
      (また, Solaris 上では schedctl を使ってみたらどうか, とのこと.)
      ---------------------------------------- -}

      // Slow-path exit - this thread must ensure succession and progress.
      // OnDeck serves as lock to protect cxq and EntryList.
      // Only the holder of OnDeck can manipulate EntryList or detach the RATs from cxq.
      // Avoid ABA - allow multiple concurrent producers (enqueue via push-CAS)
      // but only one concurrent consumer (detacher of RATs).
      // Consider protecting this critical section with schedctl on Solaris.
      // Unlike a normal lock, however, the exiting thread "locks" OnDeck,
      // picks a successor and marks that thread as OnDeck.  That successor
      // thread will then clear OnDeck once it eventually acquires the outer lock.
      if (CASPTR (&_OnDeck, NULL, _LBIT) != UNS(NULL)) {
        return ;
      }

  {- -------------------------------------------
  (1) (変数宣言など) (List はこの時点での _EntryList の値. volatile なので最適化はされない)
      ---------------------------------------- -}

      ParkEvent * List = _EntryList ;

  {- -------------------------------------------
  (1) _EntryList が空でなかった場合は, 
      _EntryList の先頭要素を _OnDeck に移して, 
      ここでリターン.

      (なお, リターン直前の時点で cxq をロックしているスレッドがいなければ, 
       リターンする前に os::PlatformEvent::unpark() を呼んで
       _OnDeck に設定したスレッドを起床させている.
       (逆に言うと, 次のスレッドが cxq をロックしていれば unpark() はそのスレッドに任せる)

       この cxq の確認は, _OnDeck や _EntryList の変更後, 
       OrderAccess::storeload() でメモリバリアを張ってから行っている.)
      ---------------------------------------- -}

      if (List != NULL) {
        // Transfer the head of the EntryList to the OnDeck position.
        // Once OnDeck, a thread stays OnDeck until it acquires the lock.
        // For a given lock there is at most OnDeck thread at any one instant.
       WakeOne:
        assert (List == _EntryList, "invariant") ;
        ParkEvent * const w = List ;
        assert (RelaxAssert || w != Thread::current()->_MutexEvent, "invariant") ;
        _EntryList = w->ListNext ;
        // as a diagnostic measure consider setting w->_ListNext = BAD
        assert (UNS(_OnDeck) == _LBIT, "invariant") ;
        _OnDeck = w ;           // pass OnDeck to w.
                                // w will clear OnDeck once it acquires the outer lock

        // Another optional optimization ...
        // For heavily contended locks it's not uncommon that some other
        // thread acquired the lock while this thread was arranging succession.
        // Try to defer the unpark() operation - Delegate the responsibility
        // for unpark()ing the OnDeck thread to the current or subsequent owners
        // That is, the new owner is responsible for unparking the OnDeck thread.
        OrderAccess::storeload() ;
        cxq = _LockWord.FullWord ;
        if (cxq & _LBIT) return ;

        w->unpark() ;
        return ;
      }

  {- -------------------------------------------
  (1) (変数宣言など) (cxq はこの時点での _LockWord の値. volatile なので最適化はされない)
      ---------------------------------------- -}

      cxq = _LockWord.FullWord ;

  {- -------------------------------------------
  (1) (_EntryList は空だが) cxq は空でなかった場合, 
      cxq の中身を _EntryList に移動させる.
      その後, WakeOne にジャンプして, 
      _EntryList が空ではなかった場合の処理を行う.

      なお, cxq の中身を _EntryList に移動させる処理は, 以下のように行う.
      (1) まず, CASPTR で _LockWord フィールド (cxq) の値を NULL に変更する.
          (他のスレッドによって cxq に要素が追加された場合には, CASPTR が失敗する可能性もある.
           このため, この処理は CASPTR が成功するか, もしくは他のスレッドが _LBIT を立ててしまうまで続ける.
           他のスレッドが _LBIT を立てた場合は, 引き継ぎ処理をそのスレッドに任せられるかもしれないため, 
           Punt にジャンプして任せるための処理を行う.)

          (ところで, 正確に言うと NULL ではなく cxq & _LBIT を書き込んでいるが, 
           これが NULL にならないケースはあるか???
           その場合は Punt にジャンプしてしまうような... #TODO)

          (なおコメントによると, 追加処理と競合したら何度も CAS を繰り返すのではなく, 
           元々あった部分だけ EntryList に移動させるという解決方法もある, とのこと)

      (2) 次に, 変更前の cxq の値を _EntryList フィールドに書き込む.
          (ところで, cxq の LBIT ビットをクリアしてから上書きしているが, 
           LBIT がたっていることがあり得るか??? 
           その場合は Punt にジャンプしてしまうような... #TODO)
      ---------------------------------------- -}

      if ((cxq & ~_LBIT) != 0) {
        // The EntryList is empty but the cxq is populated.
        // drain RATs from cxq into EntryList
        // Detach RATs segment with CAS and then merge into EntryList
        for (;;) {
          // optional optimization - if locked, the owner is responsible for succession
          if (cxq & _LBIT) goto Punt ;
          const intptr_t vfy = CASPTR (&_LockWord, cxq, cxq & _LBIT) ;
          if (vfy == cxq) break ;
          cxq = vfy ;
          // Interference - LockWord changed - Just retry
          // We can see concurrent interference from contending threads
          // pushing themselves onto the cxq or from lock-unlock operations.
          // From the perspective of this thread, EntryList is stable and
          // the cxq is prepend-only -- the head is volatile but the interior
          // of the cxq is stable.  In theory if we encounter interference from threads
          // pushing onto cxq we could simply break off the original cxq suffix and
          // move that segment to the EntryList, avoiding a 2nd or multiple CAS attempts
          // on the high-traffic LockWord variable.   For instance lets say the cxq is "ABCD"
          // when we first fetch cxq above.  Between the fetch -- where we observed "A"
          // -- and CAS -- where we attempt to CAS null over A -- "PQR" arrive,
          // yielding cxq = "PQRABCD".  In this case we could simply set A.ListNext
          // null, leaving cxq = "PQRA" and transfer the "BCD" segment to the EntryList.
          // Note too, that it's safe for this thread to traverse the cxq
          // without taking any special concurrency precautions.
        }

        // We don't currently reorder the cxq segment as we move it onto
        // the EntryList, but it might make sense to reverse the order
        // or perhaps sort by thread priority.  See the comments in
        // synchronizer.cpp objectMonitor::exit().
        assert (_EntryList == NULL, "invariant") ;
        _EntryList = List = (ParkEvent *)(cxq & ~_LBIT) ;
        assert (List != NULL, "invariant") ;
        goto WakeOne ;
      }

  {- -------------------------------------------
  (1) 以下は, 他のスレッドが _LockWord フィールド (cxq) をロックしていた場合の処理.
      この場合, 相手が cxq のロックを手放す前にこちらが _OnDeck のロックを手放してしまえれば, 
      引き継ぎ処理の責任者は相手になる.

      OnDeck のロックを解放した後 (_OnDeck を NULL にし, OrderAccess::storeload() でメモリバリアを張った後), 
      再度 cxq (_LockWord) の値を確認する (デッカーのアルゴリズムによる確認).
      この時点でまだ cxq のロックが取られていれば, 責任は相手に移ったので, このままリターン.
      逆にもう cxq が解放されていれば, 責任者は自分のままなので, Succession に戻ってやり直す.
      ---------------------------------------- -}

      // cxq|EntryList is empty.
      // w == NULL implies that cxq|EntryList == NULL in the past.
      // Possible race - rare inopportune interleaving.
      // A thread could have added itself to cxq since this thread previously checked.
      // Detect and recover by refetching cxq.
     Punt:
      assert (UNS(_OnDeck) == _LBIT, "invariant") ;
      _OnDeck = NULL ;            // Release inner lock.
      OrderAccess::storeload();   // Dekker duality - pivot point

      // Resample LockWord/cxq to recover from possible race.
      // For instance, while this thread T1 held OnDeck, some other thread T2 might
      // acquire the outer lock.  Another thread T3 might try to acquire the outer
      // lock, but encounter contention and enqueue itself on cxq.  T2 then drops the
      // outer lock, but skips succession as this thread T1 still holds OnDeck.
      // T1 is and remains responsible for ensuring succession of T3.
      //
      // Note that we don't need to recheck EntryList, just cxq.
      // If threads moved onto EntryList since we dropped OnDeck
      // that implies some other thread forced succession.
      cxq = _LockWord.FullWord ;
      if ((cxq & ~_LBIT) != 0 && (cxq & _LBIT) == 0) {
        goto Succession ;         // potential race -- re-run succession
      }
      return ;
    }

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