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完成
......
}