上一篇文章分析了如何获得监视器,本文分析如何释放监视器。
释放监视器
释放监视器主要与ExitEpilog函数和exit函数有关。
ExitEpilog函数
ExitEpilog函数在释放监视器的过程中多次用到,其代码如下所示。
void ObjectMonitor::ExitEpilog (Thread * Self, ObjectWaiter * Wakee) {
assert (_owner == Self, "invariant") ;
// Exit protocol:
// 1. ST _succ = wakee
// 2. membar #loadstore|#storestore;
// 2. ST _owner = NULL
// 3. unpark(wakee)
_succ = Knob_SuccEnabled ? Wakee->_thread : NULL ;
ParkEvent * Trigger = Wakee->_event ;
// Hygiene -- once we've set _owner = NULL we can't safely dereference Wakee again.
// The thread associated with Wakee may have grabbed the lock and "Wakee" may be
// out-of-scope (non-extant).
Wakee = NULL ;
// Drop the lock
OrderAccess::release_store_ptr (&_owner, NULL) ;
OrderAccess::fence() ; // ST _owner vs LD in unpark()
if (SafepointSynchronize::do_call_back()) {
TEVENT (unpark before SAFEPOINT) ;
}
DTRACE_MONITOR_PROBE(contended__exit, this, object(), Self);
Trigger->unpark() ;
// Maintain stats and report events to JVMTI
if (ObjectMonitor::_sync_Parks != NULL) {
ObjectMonitor::_sync_Parks->inc() ;
}
}
ExitEpilog函数的作用如下:
- 首先将监视器的_owner字段置为NULL;
- 然后使用ParkEvent类的unpark函数唤醒参数Wakee包装的线程,需要注意的是ObjectWaiter类的构造函数为ObjectWaiter类实例保存了线程用于同步的ParkEvent。
ObjectWaiter::ObjectWaiter(Thread* thread) {
_next = NULL;
_prev = NULL;
_notified = 0;
TState = TS_RUN ;
_thread = thread;
_event = thread->_ParkEvent ;
_active = false;
assert (_event != NULL, "invariant") ;
}
exit函数
exit函数代码如下:
void ATTR ObjectMonitor::exit(bool not_suspended, TRAPS) {
Thread * Self = THREAD ;
if (THREAD != _owner) {
if (THREAD->is_lock_owned((address) _owner)) { // _owner指向锁记录,如果锁记录是由参数线程在其栈桢上分配
// Transmute _owner from a BasicLock pointer to a Thread address.
// We don't need to hold _mutex for this transition.
// Non-null to Non-null is safe as long as all readers can
// tolerate either flavor.
assert (_recursions == 0, "invariant") ;
_owner = THREAD ; // 将_owner指向线程
_recursions = 0 ; // 此时肯定不是重入
OwnerIsThread = 1 ; // 当前_owner是线程指针
} else { // 锁记录不是由参数线程在其栈桢上分配,其实是出错了
// NOTE: we need to handle unbalanced monitor enter/exit
// in native code by throwing an exception.
// TODO: Throw an IllegalMonitorStateException ?
TEVENT (Exit - Throw IMSX) ;
assert(false, "Non-balanced monitor enter/exit!");
if (false) {
THROW(vmSymbols::java_lang_IllegalMonitorStateException());
}
return;
}
}
if (_recursions != 0) { // 递归重入的情况,此时参数线程还持有监视器
_recursions--; // this is simple recursive enter
TEVENT (Inflated exit - recursive) ;
return ;
}
// Invariant: after setting Responsible=null an thread must execute
// a MEMBAR or other serializing instruction before fetching EntryList|cxq.
if ((SyncFlags & 4) == 0) {
_Responsible = NULL ;
}
#if INCLUDE_TRACE
// get the owner's thread id for the MonitorEnter event
// if it is enabled and the thread isn't suspended
if (not_suspended && Tracing::is_event_enabled(TraceJavaMonitorEnterEvent)) {
_previous_owner_tid = SharedRuntime::get_java_tid(Self);
}
#endif
for (;;) {
assert (THREAD == _owner, "invariant") ; // 此时参数线程还持有监视器
// 省略一些代码,没太看懂这部分
guarantee (_owner == THREAD, "invariant") ;
ObjectWaiter * w = NULL ;
int QMode = Knob_QMode ;
// cxq链表和EntryList链表均不为空的情况,接下来的三个if很显然要求cxq链表不为空,EntryList链表不为空的要求在1188行
if (QMode == 2 && _cxq != NULL) { // Knob_QMode是2意味着cxq链表的优先级比EntryList链表高,优先从cxq中唤醒第一个线程
// QMode == 2 : cxq has precedence over EntryList.
// Try to directly wake a successor from the cxq.
// If successful, the successor will need to unlink itself from cxq.
w = _cxq ;
assert (w != NULL, "invariant") ;
assert (w->TState == ObjectWaiter::TS_CXQ, "Invariant") ;
ExitEpilog (Self, w) ;
return ;
}
if (QMode == 3 && _cxq != NULL) { // Knob_QMode是3意味着需要将cxq链表中的元素链接到EntryList链表后面
// Aggressively drain cxq into EntryList at the first opportunity.
// This policy ensure that recently-run threads live at the head of EntryList.
// Drain _cxq into EntryList - bulk transfer.
// First, detach _cxq.
// The following loop is tantamount to: w = swap (&cxq, NULL)
w = _cxq ;
for (;;) { // 使_cxq为NULL,w指向原cxq链表
assert (w != NULL, "Invariant") ;
ObjectWaiter * u = (ObjectWaiter *) Atomic::cmpxchg_ptr (NULL, &_cxq, w) ;
if (u == w) break ;
w = u ;
}
assert (w != NULL , "invariant") ;
ObjectWaiter * q = NULL ;
ObjectWaiter * p ;
for (p = w ; p != NULL ; p = p->_next) { // cxq链表是单向的,为了接到EntryList需要变为双向的
guarantee (p->TState == ObjectWaiter::TS_CXQ, "Invariant") ;
p->TState = ObjectWaiter::TS_ENTER ; // 因为会进入EntryList链表,所以将等待状态从TS_CXQ变为TS_ENTER
p->_prev = q ;
q = p ;
}
// Append the RATs to the EntryList
// TODO: organize EntryList as a CDLL so we can locate the tail in constant-time.
ObjectWaiter * Tail ;
for (Tail = _EntryList ; Tail != NULL && Tail->_next != NULL ; Tail = Tail->_next) ;
if (Tail == NULL) {
_EntryList = w ;
} else {
Tail->_next = w ;
w->_prev = Tail ;
}
// 此时原cxq链表已经链接到EntryList后面了
// Fall thru into code that tries to wake a successor from EntryList
}
if (QMode == 4 && _cxq != NULL) { // Knob_QMode是4意味着需要将cxq链表中的元素链接到EntryList链表前面
// Aggressively drain cxq into EntryList at the first opportunity.
// This policy ensure that recently-run threads live at the head of EntryList.
// Drain _cxq into EntryList - bulk transfer.
// First, detach _cxq.
// The following loop is tantamount to: w = swap (&cxq, NULL)
w = _cxq ;
for (;;) {
assert (w != NULL, "Invariant") ;
ObjectWaiter * u = (ObjectWaiter *) Atomic::cmpxchg_ptr (NULL, &_cxq, w) ;
if (u == w) break ;
w = u ;
}
assert (w != NULL , "invariant") ;
ObjectWaiter * q = NULL ;
ObjectWaiter * p ;
for (p = w ; p != NULL ; p = p->_next) {
guarantee (p->TState == ObjectWaiter::TS_CXQ, "Invariant") ;
p->TState = ObjectWaiter::TS_ENTER ;
p->_prev = q ;
q = p ;
}
// Prepend the RATs to the EntryList
if (_EntryList != NULL) {
q->_next = _EntryList ;
_EntryList->_prev = q ;
}
_EntryList = w ;
// 此时原cxq链表已经链接到EntryList链表前面了,_EntryList指向新的EntryList链表
// Fall thru into code that tries to wake a successor from EntryList
}
// 对上面两个if判断来说,w指向新的EntryList链表,若不符合上述判断条件则w依然指向原EntryList链表
w = _EntryList ;
if (w != NULL) { // 若EntryList链表不空,则唤醒EntryList链表中的第一个线程
// I'd like to write: guarantee (w->_thread != Self).
// But in practice an exiting thread may find itself on the EntryList.
// Lets say thread T1 calls O.wait(). Wait() enqueues T1 on O's waitset and
// then calls exit(). Exit release the lock by setting O._owner to NULL.
// Lets say T1 then stalls. T2 acquires O and calls O.notify(). The
// notify() operation moves T1 from O's waitset to O's EntryList. T2 then
// release the lock "O". T2 resumes immediately after the ST of null into
// _owner, above. T2 notices that the EntryList is populated, so it
// reacquires the lock and then finds itself on the EntryList.
// Given all that, we have to tolerate the circumstance where "w" is
// associated with Self.
assert (w->TState == ObjectWaiter::TS_ENTER, "invariant") ;
ExitEpilog (Self, w) ;
return ;
}
// cxq链表和EntryList链表均为空时重试
// If we find that both _cxq and EntryList are null then just
// re-run the exit protocol from the top.
w = _cxq ;
if (w == NULL) continue ;
// 以下是cxq链表不为空但EntryList链表为空的情况
// Drain _cxq into EntryList - bulk transfer.
// First, detach _cxq.
// The following loop is tantamount to: w = swap (&cxq, NULL)
for (;;) {
assert (w != NULL, "Invariant") ;
ObjectWaiter * u = (ObjectWaiter *) Atomic::cmpxchg_ptr (NULL, &_cxq, w) ;
if (u == w) break ;
w = u ;
}
TEVENT (Inflated exit - drain cxq into EntryList) ;
assert (w != NULL , "invariant") ;
assert (_EntryList == NULL , "invariant") ;
// Convert the LIFO SLL anchored by _cxq into a DLL.
// The list reorganization step operates in O(LENGTH(w)) time.
// It's critical that this step operate quickly as
// "Self" still holds the outer-lock, restricting parallelism
// and effectively lengthening the critical section.
// Invariant: s chases t chases u.
// TODO-FIXME: consider changing EntryList from a DLL to a CDLL so
// we have faster access to the tail.
if (QMode == 1) { // Knob_QMode是1意味着EntryList链表为空,需要使cxq链表成为EntryList链表并反转顺序
// QMode == 1 : drain cxq to EntryList, reversing order
// We also reverse the order of the list.
ObjectWaiter * s = NULL ;
ObjectWaiter * t = w ;
ObjectWaiter * u = NULL ;
while (t != NULL) {
guarantee (t->TState == ObjectWaiter::TS_CXQ, "invariant") ;
t->TState = ObjectWaiter::TS_ENTER ;
u = t->_next ;
t->_prev = u ;
t->_next = s ;
s = t;
t = u ;
}
_EntryList = s ;
assert (s != NULL, "invariant") ;
} else { // 其他情况只需要使cxq链表成为EntryList链表,不需要反转
// QMode == 0 or QMode == 2
_EntryList = w ;
ObjectWaiter * q = NULL ;
ObjectWaiter * p ;
for (p = w ; p != NULL ; p = p->_next) {
guarantee (p->TState == ObjectWaiter::TS_CXQ, "Invariant") ;
p->TState = ObjectWaiter::TS_ENTER ;
p->_prev = q ;
q = p ;
}
}
// In 1-0 mode we need: ST EntryList; MEMBAR #storestore; ST _owner = NULL
// The MEMBAR is satisfied by the release_store() operation in ExitEpilog().
// See if we can abdicate to a spinner instead of waking a thread.
// A primary goal of the implementation is to reduce the
// context-switch rate.
if (_succ != NULL) continue;
w = _EntryList ;
if (w != NULL) { // 唤醒新EntryList链表中的第一个线程
guarantee (w->TState == ObjectWaiter::TS_ENTER, "invariant") ;
ExitEpilog (Self, w) ;
return ;
}
}
}
exit函数的简要功能如下:
- 首先检查参数线程是否持有监视器,如果是递归重入的情况直接返回,否则继续;
- 然后根据cxq链表和EntryList链表的不同情况和Knob_QMode值唤醒不同链表上的线程。
cxq链表和EntryList链表的情况有以下三种:
- cxq链表和EntryList链表均不为空的情况:
- Knob_QMode是2意味着cxq链表的优先级比EntryList链表高,优先从cxq中唤醒第一个线程;
- Knob_QMode是3意味着需要将cxq链表中的元素链接到EntryList链表后面,然后唤醒新EntryList链表中的第一个线程;
- Knob_QMode是4意味着需要将cxq链表中的元素链接到EntryList链表前面,然后唤醒新EntryList链表中的第一个线程;
- 其他情况唤醒EntryList链表中的第一个线程;
- cxq链表和EntryList链表均为空时需要重试;
- cxq链表不为空但EntryList链表为空的情况:
- Knob_QMode是1意味着EntryList链表为空,需要使cxq链表成为EntryList链表并反转顺序,然后唤醒新EntryList链表中的第一个线程;
- 其他情况只需要使cxq链表成为EntryList链表,不需要反转,然后唤醒新EntryList链表中的第一个线程。
Knob_QMode是定义在文件中的objectMonitor.cpp全局变量,默认值是0。我没有找到如何在启动虚拟机时配置该值,但可以在编译时修改代码指定。
网友评论