hotspot/src/share/vm/runtime/mutex.cpp
void Monitor::IUnlock (bool RelaxAssert) {
{- -------------------------------------------
(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.