hotspot/src/share/vm/runtime/mutex.cpp
int Monitor::IWait (Thread * Self, jlong timo) {
{- -------------------------------------------
(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.