1. Java 中的 Monitor
Java synchronized 關鍵字 中介紹過synchronize
的實現原理,其實無論是同步方法ACC_SYNCHRONIZED
還是同步代碼塊 monitorenter
、monitorexit
都是基於Monitor
實現的。Monitor
是 Java 中用以實現線程之間的互斥與協作的主要手段,它可以看成是對象的鎖,每一個對象都有且僅有一個 Monitor
。它位於 Java 對象的對象頭裏面,職責是保證同一時間只有一個線程能夠訪問被保護的數據和代碼,其發生作用時的特點是:
- 對象的目標代碼都被互斥地執行。一個
Monitor
只允許一個線程持有,任一個線程訪問被保護的代碼都需要獲得這個“許可”,執行完畢時釋放所有權 - 提供信號機制:允許正持有
Monitor
的線程暫時放棄持有,等待某種條件成立後,進程可以通知正在等待這個條件變量的線程,使其可以重新去嘗試持有Monitor
2. Monitor 實現鎖分配
2.1 Monitor 的結構
在 Java 虛擬機 (HotSpot)中,Monitor
基於C++
實現,其數據結構爲 objectMonitor.hpp ,比較關鍵的屬性如下:
ObjectMonitor() {
......
// 用來記錄該線程獲取鎖的次數
_count = 0;
// 鎖的重入次數
_recursions = 0;
// 指向持有ObjectMonitor對象的線程
_owner = NULL;
// 存放處於wait狀態的線程隊列
_WaitSet = NULL;
// 存放處於等待鎖block狀態的線程隊列
_EntryList = NULL ;
......
}
鎖相關主要的流程如下:
- 當多個線程同時訪問一段同步代碼時,首先進入
_EntryList
隊列中,當某個線程獲取到對象的Monitor
後進入_Owner
區域並把Monitor
中的_owner
變量設置爲當前線程,同時Monitor
中的計數器_count
自增1,表示獲得對象鎖 - 若持有
Monitor
的線程調用wait()
方法,將釋放當前持有的Monitor
,_owner
變量恢復爲 null,_count
自減1,同時該線程進入_WaitSet
集合中等待被喚醒。若當前線程執行完畢也會釋放Monitor(鎖)
並復位變量的值,以便其他線程能夠獲取Monitor(鎖)
- 在
Entry Set
中等待的線程狀態是Waiting for monitor entry
,而在Wait Set
中等待的線程狀態是in Object.wait()
2.2 獲取鎖流程
objectMonitor.cpp 爲實現鎖相關操作的關鍵類,其中 enter()
函數爲其重量級鎖獲取的入口
void ATTR ObjectMonitor::enter(TRAPS) {
Thread * const Self = THREAD ;
void * cur ;
// CAS嘗試把monitor的`_owner`字段設置爲當前線程
cur = Atomic::cmpxchg_ptr (Self, &_owner, NULL) ;
// 獲取鎖失敗
if (cur == NULL) {
// Either ASSERT _recursions == 0 or explicitly set _recursions = 0.
assert (_recursions == 0 , "invariant") ;
assert (_owner == Self, "invariant") ;
// CONSIDER: set or assert OwnerIsThread == 1
return ;
}
// 舊值和當前線程一樣,則當前線程已經持有鎖,此次爲重入,_recursions自增,並獲得鎖
if (cur == Self) {
// TODO-FIXME: check for integer overflow! BUGID 6557169.
_recursions ++ ;
return ;
}
// 當前線程是第一次進入該 monitor,設置_recursions爲1,_owner爲當前線程
if (Self->is_lock_owned ((address)cur)) {
assert (_recursions == 0, "internal state error");
_recursions = 1 ;
// Commute owner from a thread-specific on-stack BasicLockObject address to
// a full-fledged "Thread *".
_owner = Self ;
OwnerIsThread = 1 ;
return ;
}
// We've encountered genuine contention.
assert (Self->_Stalled == 0, "invariant") ;
Self->_Stalled = intptr_t(this) ;
// Try one round of spinning *before* enqueueing Self
// and before going through the awkward and expensive state
// transitions. The following spin is strictly optional ...
// Note that if we acquire the monitor from an initial spin
// we forgo posting JVMTI events and firing DTRACE probes.
if (Knob_SpinEarly && TrySpin (Self) > 0) {
assert (_owner == Self , "invariant") ;
assert (_recursions == 0 , "invariant") ;
assert (((oop)(object()))->mark() == markOopDesc::encode(this), "invariant") ;
Self->_Stalled = 0 ;
return ;
}
assert (_owner != Self , "invariant") ;
assert (_succ != Self , "invariant") ;
assert (Self->is_Java_thread() , "invariant") ;
JavaThread * jt = (JavaThread *) Self ;
assert (!SafepointSynchronize::is_at_safepoint(), "invariant") ;
assert (jt->thread_state() != _thread_blocked , "invariant") ;
assert (this->object() != NULL , "invariant") ;
assert (_count >= 0, "invariant") ;
// Prevent deflation at STW-time. See deflate_idle_monitors() and is_busy().
// Ensure the object-monitor relationship remains stable while there's contention.
Atomic::inc_ptr(&_count);
EventJavaMonitorEnter event;
{ // Change java thread status to indicate blocked on monitor enter.
JavaThreadBlockedOnMonitorEnterState jtbmes(jt, this);
Self->set_current_pending_monitor(this);
DTRACE_MONITOR_PROBE(contended__enter, this, object(), jt);
if (JvmtiExport::should_post_monitor_contended_enter()) {
JvmtiExport::post_monitor_contended_enter(jt, this);
// The current thread does not yet own the monitor and does not
// yet appear on any queues that would get it made the successor.
// This means that the JVMTI_EVENT_MONITOR_CONTENDED_ENTER event
// handler cannot accidentally consume an unpark() meant for the
// ParkEvent associated with this ObjectMonitor.
}
OSThreadContendState osts(Self->osthread());
ThreadBlockInVM tbivm(jt);
// 自旋執行 ObjectMonitor::EnterI 方法等待鎖的釋放
for (;;) {
jt->set_suspend_equivalent();
// cleared by handle_special_suspend_equivalent_condition()
// or java_suspend_self()
EnterI (THREAD) ;
if (!ExitSuspendEquivalent(jt)) break ;
//
// We have acquired the contended monitor, but while we were
// waiting another thread suspended us. We don't want to enter
// the monitor while suspended because that would surprise the
// thread that suspended us.
//
_recursions = 0 ;
_succ = NULL ;
exit (false, Self) ;
jt->java_suspend_self();
}
Self->set_current_pending_monitor(NULL);
// We cleared the pending monitor info since we've just gotten past
// the enter-check-for-suspend dance and we now own the monitor free
// and clear, i.e., it is no longer pending. The ThreadBlockInVM
// destructor can go to a safepoint at the end of this block. If we
// do a thread dump during that safepoint, then this thread will show
// as having "-locked" the monitor, but the OS and java.lang.Thread
// states will still report that the thread is blocked trying to
// acquire it.
}
......
OM_PERFDATA_OP(ContendedLockAttempts, inc());
}
2.3 釋放鎖流程
objectMonitor.cpp 的 exit()
函數爲釋放鎖操作,其大致流程如下
void ATTR ObjectMonitor::exit(bool not_suspended, TRAPS) {
Thread * Self = THREAD ;
// 如果當前線程未持有 Monitor
if (THREAD != _owner) {
if (THREAD->is_lock_owned((address) _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 ;
_recursions = 0 ;
OwnerIsThread = 1 ;
} 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;
}
}
// 當前線程持有 Monitor,如果_recursions次數不爲0,自減
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 ;
}
// 根據不同的 QMode,從 cxq 或 _EntryList 中獲取頭節點,通過ObjectMonitor::ExitEpilog方法喚醒
// 該節點封裝的線程,喚醒操作最終由unpark完成
......
}