锁膨胀与重量级锁
介绍
锁膨胀主要发生在竞争较大的情况下,发生在通过轻量级手段无法获取到目标锁的时候,就会执行锁膨胀,锁膨胀的目标是为了实现重量级锁相关操作。
锁膨胀的主要功能是返回线程与锁关联的ObjectMonitor,主要有4种情况:
1、在锁已经膨胀的情况下返回膨胀后的ObjectMonitor
2、在锁正在膨胀的情况下进行让步自旋等操作,让其膨胀完
3、在锁是轻量级锁产生竞争的情况下,撤销与轻量级锁关联的BasicObjectLock,改为膨胀状态的ObjectMonitor
4、生成新的ObjectMonitor,主要用于在无锁状态下,直接膨胀
重量级锁实现
上面介绍了锁膨胀的功能是返回ObjectMonitor,ObjectMonitor对象就是实现synchronized重量级锁的核心类。synchronized底层主要还是使用mutex lock+阻塞队列+线程自旋实现了锁竞争,线程等待功能。因为mutex会让线程等待,让出CPU执行权,导致上下文切换,性能开销比较大,因此叫做重量级锁,该锁底层线程与锁关联是独立的数据结构和API,就是ObjectMonitor不依赖BasicObjectLock。ObjectMonitor提供了管理线程,维护竞争线程状态等功能,而BasicObjectLock只负责持有线程维护管理,因此ObjectMonitor功能要强于BasicObjectLock。
锁膨胀源码分析
上面介绍了,锁膨胀主要分为4种情况,接下来从源码证明
源码位置位于synchronizer.cpp ObjectSynchronizer::inflate
注意下面的代码直接从for (;;) 开始分析,这个死循环的主要目的是自旋膨胀
// 获取到锁对象的mark
const markOop mark = object->mark() ;
assert (!mark->has_bias_pattern(), "invariant") ;
// The mark can be in one of the following states: 根据上面mark会出现的情况:
// * Inflated - just return 已经膨胀过了,只返回
// * Stack-locked - coerce it to inflated 轻量级锁,线程栈中有lock,这里的locked指的就是栈帧中有BasicObjectLock
// * INFLATING - busy wait for conversion to complete 正在膨胀
// * Neutral - aggressively inflate the object. 无锁状态,直接膨胀
// * BIASED - Illegal. We should never see this 偏向锁,在这里不会出现,前面代码第二行逻辑就是断言不可能出现偏向锁。
// 通过对上面注释查看,正好对应我们分析的4种状态
在锁已经膨胀的情况下返回膨胀后的ObjectMonitor
// CASE: inflated
if (mark->has_monitor()) {
ObjectMonitor * inf = mark->monitor() ;
assert (inf->header()->is_neutral(), "invariant");
assert (inf->object() == object, "invariant") ;
assert (ObjectSynchronizer::verify_objmon_isinpool(inf), "monitor is invalid");
// 已膨胀后,直接返回膨胀后的状态,上面的断言主要是检查,这是一种很好的习惯,平常我们可以把这种习惯应用在日常开发中,断言一些主要的业务逻辑因为数据导致的错误
return inf ;
}
在锁正在膨胀的情况下进行让步自旋等操作,让其膨胀完
// 可以看到这里判断了mark的状态等于膨胀中,这种出现的原因主要发生在轻量级锁竞争失败,然后执行膨胀,膨胀过程中,持有轻量级锁又释放了,也执行膨胀,就会进入这里,简而言之这里就是当多线程竞争膨胀的时候执行
// 执行原理:当前线程通过某些手段让出执行,自旋等待膨胀过程完成后直接返回,注意这里分析的代码在一个死循环里面
if (mark == markOopDesc::INFLATING()) {
TEVENT (Inflate: spin while INFLATING) ;
// 这里的代码就是适量的让出线程,自旋操作
ReadStableMark(object) ;
continue ;
}
在锁是轻量级锁产生竞争的情况下,撤销与轻量级锁关联的BasicObjectLock,改为膨胀状态的ObjectMonitor
// 对象头有lock指针,也就是轻量级锁
if (mark->has_locker()) {
// 分配一个Objectmonitor
ObjectMonitor * m = omAlloc (Self) ;
// Optimistically prepare the objectmonitor - anticipate successful CAS
// We do this before the CAS in order to minimize the length of time
// in which INFLATING appears in the mark.
m->Recycle();
m->_Responsible = NULL ;
m->OwnerIsThread = 0 ;
m->_recursions = 0 ;
m->_SpinDuration = ObjectMonitor::Knob_SpinLimit ; // Consider: maintain by type/class
// 设置锁对象状态为正在膨胀,对应了上面第二种情况
markOop cmp = (markOop) Atomic::cmpxchg_ptr (markOopDesc::INFLATING(), object->mark_addr(), mark) ;
if (cmp != mark) {
// 进入到这里是少数了,就是多线程在竞争锁膨胀状态
omRelease (Self, m, true) ;
continue ; // Interference -- just retry
}
// 剩下的都是些设置ObjectMonitor属性了
markOop dmw = mark->displaced_mark_helper() ;
assert (dmw->is_neutral(), "invariant") ;
// Setup monitor fields to proper values -- prepare the monitor
m->set_header(dmw) ;
m->set_owner(mark->locker());
m->set_object(object);
// TODO-FIXME: assert BasicLock->dhw != 0.
guarantee (object->mark() == markOopDesc::INFLATING(), "invariant") ;
object->release_set_mark(markOopDesc::encode(m));
// Hopefully the performance counters are allocated on distinct cache lines
// to avoid false sharing on MP systems ...
if (ObjectMonitor::_sync_Inflations != NULL) ObjectMonitor::_sync_Inflations->inc() ;
TEVENT(Inflate: overwrite stacklock) ;
if (TraceMonitorInflation) {
if (object->is_instance()) {
ResourceMark rm;
tty->print_cr("Inflating object " INTPTR_FORMAT " , mark " INTPTR_FORMAT " , type %s",
(void *) object, (intptr_t) object->mark(),
object->klass()->external_name());
}
}
return m ;
}
生成新的ObjectMonitor,主要用于在无锁状态下,直接膨胀
// 断言:必须是无锁才能执行下面逻辑
assert (mark->is_neutral(), "invariant");
ObjectMonitor * m = omAlloc (Self) ;// 申请新的ObjectMonitor
m->Recycle();
m->set_header(mark);
m->set_owner(NULL);
m->set_object(object);
m->OwnerIsThread = 1 ;
m->_recursions = 0 ;
m->_Responsible = NULL ;
m->_SpinDuration = ObjectMonitor::Knob_SpinLimit ; // consider: keep metastats by type/class
// 变换锁状态了:设置成重量级锁
if (Atomic::cmpxchg_ptr (markOopDesc::encode(m), object->mark_addr(), mark) != mark) {
// 进入了这里,说明了多线程在无锁状态下执行锁膨胀竞争,因此需要自旋
m->set_object (NULL) ;
m->set_owner (NULL) ;
m->OwnerIsThread = 0 ;
m->Recycle() ;
omRelease (Self, m, true) ;
m = NULL ;
continue ;
}
if (ObjectMonitor::_sync_Inflations != NULL) ObjectMonitor::_sync_Inflations->inc() ;
TEVENT(Inflate: overwrite neutral) ;
if (TraceMonitorInflation) {
if (object->is_instance()) {
ResourceMark rm;
tty->print_cr("Inflating object " INTPTR_FORMAT " , mark " INTPTR_FORMAT " , type %s",
(void *) object, (intptr_t) object->mark(),
object->klass()->external_name());
}
}
return m ;
以上就是锁膨胀的所有源码逻辑,锁膨胀在synchronized锁的相关源码中逻辑算是比较简单的
具体流程如下:
重量级锁之ObjectMonitor
介绍
上面分析了锁膨胀方法ObjectSynchronizer::inflate主要的作用是返回膨胀后的ObjectMonitor对象,后续的加锁解锁逻辑都是基于该对象操作的。
加锁:ObjectSynchronizer::inflate(THREAD, obj())->enter(THREAD) ;
释放锁:ObjectSynchronizer::inflate(THREAD, object)->exit (true, THREAD);
因此ObjectMonitor就是我们所谓的重量级锁,也称为监视锁,并且ObjectMonitor也还是Object的wait和notify的实现,这也是为什么调用wait和notify或者notifyAll方法的时候需要先获取锁的原因。
重量级锁适用于多线程竞争强烈的情况下,在竞争锁失败后会使用mutex命令进入等待状态,让出执行权等待唤醒从而造成了一定的上下文切换,导致效率较于其它几种锁性能较低。
原理:
ObjectMonitor对象原理,主要分析其中几个重要属性
定义在ObjectMonitor的定义位于hotspot\src\share\vm\runtime\objectMonitor.hpp中
这里只贴了几个重要属性,还有其它相关属性,可以查看源码注释
volatile markOop _header; // 锁对象oop的原始对象头 void* volatile _object; // 关联的锁对象oop void * volatile _owner; // 占用当前锁的线程 ObjectWaiter * volatile _cxq ; // 在锁被占用期间,获取存放锁失败的线程的队列和_EntryList协作使用 ObjectWaiter * volatile _EntryList ; // EntryList 链表头元素,该链表里面存储了因为竞争失败的线程相关信息 volatile intptr_t _count; // 抢占该锁的线程数 ObjectWaiter * volatile _WaitSet; // 调用wait方法后等待的ObjectWaiter链表大致的流程如下:
源码分析
源码分析主要还是从slow_enter和slow_exit开始,因为这两块的源码在前面偏向锁和轻量级锁都已经分析了,在执行锁膨胀后,执行获取锁逻辑分别是
加锁:ObjectSynchronizer::inflate(THREAD, obj())->enter(THREAD) ;
释放锁:ObjectSynchronizer::inflate(THREAD, object)->exit (true, THREAD);
因此直接分析Object的enter和exit。
ObjectMonitor定义在:hotspot\src\share\vm\runtime\objectMonitor.hpp;源码在:hotspot/src/share/vm/runtime/objectMonitor.cpp。
enter
首先带着问题来读源码:
1、有哪几种情况可以在enter获取锁成功,并且成功后都执行了什么操作?
2、重量级锁竞争锁的方式是什么,有哪些核心操作?
void ATTR ObjectMonitor::enter(TRAPS) {
Thread * const Self = THREAD ;
void * cur ;
// case1:通过CAS尝试获取锁,如果owner没有指向线程,则将其指向self(当前线程)成功返回比较值,不成功返回预期值
cur = Atomic::cmpxchg_ptr (Self, &_owner, NULL) ;
if (cur == NULL) {
// 设置成功:当前ObjectMonitor没有被持有
assert (_recursions == 0 , "invariant") ;
assert (_owner == Self, "invariant") ;
return ;
}
// case2:虽然设置失败了,但是当前owner就指向当前线程,则执行重入_recursions++
if (cur == Self) {
_recursions ++ ;
return ;
}
// case3:如果是轻量级锁升级过来的。判断依据在: 锁膨胀的if(mark->has_locker())分支里面;m->set_owner(mark->locker());
if (Self->is_lock_owned ((address)cur)) {
assert (_recursions == 0, "internal state error");
// 因为是轻量级锁升级上来的,说明是重入锁,则将重入次数+1
/**
在执行这个代码块的时候,执行doSomething1的时候,其它线程已经将该锁膨胀为轻量级锁了,因此这里是重入的逻辑;是不是还在想如果有第三层synchronized,那这里凭什么_recursions = 1;重入次数为1。如果有第三层直接执行上面case2了,因为这里进入这里,已经将_owner指向了Self,后面就不会执行到这里了,在case2就直接返回了。
synchronized(lock){
doSomething1
synchronized(lock){
doSomething2
}
}
*/
_recursions = 1 ;
_owner = Self ;
OwnerIsThread = 1 ;
return ;
}
assert (Self->_Stalled == 0, "invariant") ;
Self->_Stalled = intptr_t(this) ;
// case4:这里就是重量级锁,获取锁的逻辑,自旋主要是TrySpin这个方法抢占锁
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 ;
}
// 抢占失败,对正在等待的线程数量+1
Atomic::inc_ptr(&_count);
EventJavaMonitorEnter event;
{ // Change java thread status to indicate blocked on monitor enter.重置线程状态到到进入enter方法前
JavaThreadBlockedOnMonitorEnterState jtbmes(jt, this);
DTRACE_MONITOR_PROBE(contended__enter, this, object(), jt);
if (JvmtiExport::should_post_monitor_contended_enter()) {
JvmtiExport::post_monitor_contended_enter(jt, this);
}
OSThreadContendState osts(Self->osthread());
ThreadBlockInVM tbivm(jt);
Self->set_current_pending_monitor(this);
for (;;) {
jt->set_suspend_equivalent();
// case5:上面case4已经抢占锁失败了,就进入该方法,挂起锁,挂起之前还会抢占一次锁
// 在这个方法里面就是synchronized阻塞的真正逻辑,以及被唤醒后的执行逻辑,后面会分析
EnterI (THREAD) ;
if (!ExitSuspendEquivalent(jt)) break ;
_recursions = 0 ;
_succ = NULL ;
exit (false, Self) ;
jt->java_suspend_self();
}
Self->set_current_pending_monitor(NULL);
}
// 获取锁成功,在该线程被唤醒的情况下获取的
Atomic::dec_ptr(&_count);
................省略
}
可以看到上面对应了4个case,分别是case1无锁,case2重入,case3轻量级锁重入,case4获取重量级锁,case5获取锁失败。前三个都比较简单,逻辑一目了然,重要的是在是在case4和case5,需要分析其获取锁的方式和阻塞方式。
TrySpin( TrySpin_VaryDuration )
上面分析了,获取重量级锁的逻辑主要就是在这里面,根据名字可以看出是自旋的模式获取锁的。
int ObjectMonitor::TrySpin_VaryDuration (Thread * Self) {
// 固定次数抢占,这也是体现了自适应自旋的重要变量,在1.7以前是固定值10,后改成了自适应自旋,因此该值就默认为0,
int ctr = Knob_FixedSpin ;
if (ctr != 0) {
while (--ctr >= 0) {
if (TryLock (Self) > 0) return 1 ;
SpinPause () ;
}
return 0 ;
}
// TryLock就是尝试获取锁的方法
for (ctr = Knob_PreSpin + 1; --ctr >= 0 ; ) {
if (TryLock(Self) > 0) {
int x = _SpinDuration ;
if (x < Knob_SpinLimit) {
if (x < Knob_Poverty) x = Knob_Poverty ;
// 自适应自旋,如果获取锁成功,则下次自旋持续时间长一点,也就是次数多一点
_SpinDuration = x + Knob_BonusB ;
}
return 1 ;
}
// 这个方法给我的感觉就是获取锁失败,然后让CPU停一下,再尝试获取锁
SpinPause () ;
}
// 剩下的代码省略,一是我确实看着有点不懂,二是没有关键加锁释放锁逻辑;我猜测下面的逻辑应该就是优化锁获取,自适应自旋的相关参数计算
}
int ObjectMonitor::TryLock (Thread * Self) {
for (;;) {
void * own = _owner ;
if (own != NULL) return 0 ;
if (Atomic::cmpxchg_ptr (Self, &_owner, NULL) == NULL) {
// Either guarantee _recursions == 0 or set _recursions = 0.
assert (_recursions == 0, "invariant") ;
assert (_owner == Self, "invariant") ;
// CONSIDER: set or assert that OwnerIsThread == 1
return 1 ;
}
// The lock had been free momentarily, but we lost the race to the lock.
// Interference -- the CAS failed.
// We can either return -1 or retry.
// Retry doesn't make as much sense because the lock was just acquired.
if (true) return -1 ;
}
}
根据源码分析得出结论,synchronized竞争锁获取锁的主要逻辑是通过自适应自旋,然后调用TryLock尝试通过CAS将_owner指向当前线程,如果指向成功则加锁成功,否则获取锁失败,自旋重新获取。
EnterI
上面分析的TrySpin是对应了enter方法的case4,尝试竞争锁,而竞争锁失败后会执行EnterI方法,这个方法就是阻塞线程管理线程的重要方法
void ATTR ObjectMonitor::EnterI (TRAPS) {
Thread * Self = THREAD ;
assert (Self->is_Java_thread(), "invariant") ;
assert (((JavaThread *) Self)->thread_state() == _thread_blocked , "invariant") ;
// case1:阻塞前再尝试一下
if (TryLock (Self) > 0) {
assert (_succ != Self , "invariant") ;
assert (_owner == Self , "invariant") ;
assert (_Responsible != Self , "invariant") ;
return ;
}
DeferredInitialize () ;
if (TrySpin (Self) > 0) {
assert (_owner == Self , "invariant") ;
assert (_succ != Self , "invariant") ;
assert (_Responsible != Self , "invariant") ;
return ;
}
// case2:这时候获取锁失败了,即将进入阻塞
// The Spin failed -- Enqueue and park the thread ...
assert (_succ != Self , "invariant") ;
assert (_owner != Self , "invariant") ;
assert (_Responsible != Self , "invariant") ;
// 初始化ObjectWaiter,ObjectWaiter这个对象就是ObjectMonitor中的_EntryList的类型,该对象可以简单理解将阻塞线程包装成了一个链表节点
ObjectWaiter node(Self) ;
Self->_ParkEvent->reset() ;
node._prev = (ObjectWaiter *) 0xBAD ;
node.TState = ObjectWaiter::TS_CXQ ;
// case3:尝试将当前线程阻塞节点塞到头部,因为可能会有多线程来竞争,也就是阻塞,因此这里需要用循环,并且每次塞失败了,就会再尝试获取一下锁,这也体现了synchronized的非公平性;
// 注意:_cxq队列和_EntryList队列的区别?这里的_cxq队列并不是_EntryList队列,这个_cxq可以代表持有锁期间竞争失败的线程队列;而_EntryList是确定已经竞争失败,已经阻塞了的线程,你看这里加入队列后线程还没有阻塞吧。
// 想一想那这个_EntryList什么时候添加元素呢?如果我在调用park后可以确定线程阻塞了吧,那我把线程放入EntryList是不是就很合理了呢?答案肯定是不合理的,你想都已经阻塞了,已经执行不下去了怎么放入EntryList;好,那我在park之前,放入EntryList行吗?答案还是不合理,我现在都还没阻塞,你把我放进队列,万一放到一半锁被抢了,我是入队还是不如队呢,这不是很尴尬。完美解决方案,这个_EntryList添加元素是必须要在线程释放锁的时候才添加,当一个线程释放锁的时候,你想这时候_cxq的是不是肯定是阻塞了的,肯定是竞争锁失败了的线程,这时候我释放锁后再把非唤醒的线程给放入_EntryList是不是就完美的解决了_EntryList放入节点时机的问题,因此要看_EntryList何时放入,需要看exit的源码。
// 再拓展一下:其实AQS也是基于这种思想处理的阻塞节点。
ObjectWaiter * nxt ;
for (;;) {
node._next = nxt = _cxq ;
if (Atomic::cmpxchg_ptr (&node, &_cxq, nxt) == nxt) break ;
// Interference - the CAS failed because _cxq changed. Just retry.
// As an optional optimization we retry the lock.
if (TryLock (Self) > 0) {
assert (_succ != Self , "invariant") ;
assert (_owner == Self , "invariant") ;
assert (_Responsible != Self , "invariant") ;
return ;
}
}
if ((SyncFlags & 16) == 0 && nxt == NULL && _EntryList == NULL) {
// Try to assume the role of responsible thread for the monitor.
// CONSIDER: ST vs CAS vs { if (Responsible==null) Responsible=Self }
Atomic::cmpxchg_ptr (Self, &_Responsible, NULL) ;
}
TEVENT (Inflated enter - Contention) ;
int nWakeups = 0 ;
int RecheckInterval = 1 ;
// case4:阻塞和唤醒的重要逻辑
for (;;) {
// 阻塞前的最后挣扎
if (TryLock (Self) > 0) break ;
assert (_owner != Self, "invariant") ;
if ((SyncFlags & 2) && _Responsible == NULL) {
Atomic::cmpxchg_ptr (Self, &_Responsible, NULL) ;
}
// park self,使用park阻塞线程
if (_Responsible == Self || (SyncFlags & 1)) {
TEVENT (Inflated enter - park TIMED) ;
// park底层就是调用mutex
Self->_ParkEvent->park ((jlong) RecheckInterval) ;
// Increase the RecheckInterval, but clamp the value.
RecheckInterval *= 8 ;
if (RecheckInterval > 1000) RecheckInterval = 1000 ;
} else {
TEVENT (Inflated enter - park UNTIMED) ;
Self->_ParkEvent->park() ;
}
// 唤醒了,立马尝试抢占一波
if (TryLock(Self) > 0) break ;
TEVENT (Inflated enter - Futile wakeup) ;
if (ObjectMonitor::_sync_FutileWakeups != NULL) {
ObjectMonitor::_sync_FutileWakeups->inc() ;
}
++ nWakeups ;
if ((Knob_SpinAfterFutile & 1) && TrySpin (Self) > 0) break ;
if ((Knob_ResetEvent & 1) && Self->_ParkEvent->fired()) {
Self->_ParkEvent->reset() ;
OrderAccess::fence() ;
}
if (_succ == Self) _succ = NULL ;
OrderAccess::fence() ;
}
// 被阻塞后重新抢占到了锁
assert (_owner == Self , "invariant") ;
assert (object() != NULL , "invariant") ;
// 移除当前阻塞节点了,因为已经抢占到了
UnlinkAfterAcquire (Self, &node) ;
if (_succ == Self) _succ = NULL ;
assert (_succ != Self, "invariant") ;
if (_Responsible == Self) {
_Responsible = NULL ;
OrderAccess::fence(); // Dekker pivot-point
}
if (SyncFlags & 8) {
OrderAccess::fence() ;
}
return ;
}
exit
根据我们前面分析轻量级锁和其它相关逻辑的时候,可以发现synchronized在释放锁的时候也就是执行monitorexit指令在膨胀后,最后都会
ObjectMonitor调用exit方法来释放锁。该方法的主要目的是将owner属性设置为NULL,并唤醒在EntryList等待的线程。
ObjectSynchronizer::inflate(THREAD, object)->exit (true, THREAD) ;
接下来分析exit源码:
void ATTR ObjectMonitor::exit(bool not_suspended, TRAPS) {
Thread * Self = THREAD ;
//case1: 当前解锁线程也就是调用exit的不是持有锁线程
if (THREAD != _owner) {
/*
可能这里有人疑问了,monitorenter和monitorexit不是成对出现吗?为什么在释放锁的时候还会存在没有持有锁的情况?
这是在持有轻量级锁期间,被升级为重量级锁了,然后持有轻量级锁在释放重量级锁的时候才会执行这个逻辑
举个例子:Thread1持有轻量级锁,开始执行代码;Thread2竞争锁,竞争失败,调用inflat膨胀重量级锁,并进入enter逻辑无法竞争到锁,等 待此时膨胀后的重量级锁_owner为null,Thread1释放轻量级锁的时候,发现已经膨胀也调用inflat拿到ObjectMonitor执行exit,此时就会进入该逻辑。
*/
// 因为是轻量级锁释放,则直接将重入次数改为0,然后将_owner重新指向持有锁,虽然即将释放,但是不还没执行释放逻辑嘛,因此_owner还得指向当前线程。
if (THREAD->is_lock_owned((address) _owner)) {
assert (_recursions == 0, "invariant") ;
_owner = THREAD ;
_recursions = 0 ;
OwnerIsThread = 1 ;
} else {
... 走到这里我觉得多少有点毛病了,说明是其它线程占用了锁,直接返回表示本次释放锁失败了
// 不过这里面有一段代码我实在无法理解:
// if (false) 写在这儿干嘛,有点无法理解?
if (false) {
THROW(vmSymbols::java_lang_IllegalMonitorStateException());
}
return ;
}
}
//重入锁释放逻辑,直接将重入次数-1,然后返回
if (_recursions != 0) {
_recursions--; // this is simple recursive enter
TEVENT (Inflated exit - recursive) ;
return ;
}
if ((SyncFlags & 4) == 0) {
_Responsible = NULL ;
}
// 正式释放锁逻辑
for (;;) {
assert (THREAD == _owner, "invariant") ;
// static int Knob_ExitPolicy = 0 ; 默认为0
if (Knob_ExitPolicy == 0) {
// 注意这里,再次体现了不公平性,释放锁,将_owner设置为NULL,如果一个线程正在TrySpin获取锁,则会获取成功,阻塞队列的继续等待
OrderAccess::release_store_ptr (&_owner, NULL) ; // drop the lock
// 设置完后立马刷新
OrderAccess::storeload() ; // See if we need to wake a successor
// 如果EntryList是一个空链表,则直接返回,表示没有正在等待的线程
if ((intptr_t(_EntryList)|intptr_t(_cxq)) == 0 || _succ != NULL) {
TEVENT (Inflated exit - simple egress) ;
return ;
}
TEVENT (Inflated exit - complex egress) ;
// 下面条件成立,说明上面的不公平性已经出现了,也就是刚释放完马上被抢占了,并且阻塞队列 还有值
if (Atomic::cmpxchg_ptr (THREAD, &_owner, NULL) != NULL) {
return ;
}
TEVENT (Exit - Reacquired) ;
} else {
// 暂时没有找到什么条件下会进入这里,但是看到这个分支里面也有释放锁的逻辑
}
ObjectWaiter * w = NULL ;
int QMode = Knob_QMode ;
// ***********************************************************************************
/**
注意:这里有4中策略来唤醒节点和处理阻塞节点,同时这里也回答了EnterI方法中的_cxp队列和_EntryList队列交换规则
Qmodel简单理解就是,当锁竞争的时候,唤醒和出入队的策略模式选择,这个有时间,是可以通过自定义构造线程来一一测试的
QMode=2:优先唤醒_cxq中的线程,也就是持有锁期间别阻塞的线程;
QMode=3:将_cxq队列节点放入_EntryList尾部,然后从_EntryList中唤醒节点;
QMode=4:将_cxq放入_EntryList队列头部,然后从_EntryList中唤醒节点
QMode=1:优先从_EntryList队列唤醒,如果_EntryList为空,则先将_cxq队列元素,迁移到_EntryList中去
*/
// ***********************************************************************************
// 对模式总结完后,接下来分析,当OMode==2的时候,并且_cxq不为null,则w=_cxq队头,然后调用ExitEpilog唤醒
if (QMode == 2 && _cxq != NULL) {
w = _cxq ;
assert (w != NULL, "invariant") ;
assert (w->TState == ObjectWaiter::TS_CXQ, "Invariant") ;
ExitEpilog (Self, w) ;
return ;
}
// 当OMode==3的时候,迁移_cxq到_EntryList前面
if (QMode == 3 && _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 ;
}
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 ;
}
}
// 当OMode==4的时候,将_cxq迁移到EntryList后面
if (QMode == 4 && _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 ;
// Fall thru into code that tries to wake a successor from EntryList
}
// 唤醒OModel3或者Omode4策略的接待您,可能是队头也可能是队尾
w = _EntryList ;
if (w != NULL) {
assert (w->TState == ObjectWaiter::TS_ENTER, "invariant") ;
ExitEpilog (Self, w) ;
return ;
}
w = _cxq ;
if (w == NULL) continue ;
for (;;) {
assert (w != NULL, "Invariant") ;
ObjectWaiter * u = (ObjectWaiter *) Atomic::cmpxchg_ptr (NULL, &_cxq, w) ;
if (u == w) break ;
w = u ;
}
// 优先从_EntryList队列唤醒,如果_EntryList为空,则先将_cxq队列元素,迁移到_EntryList中去
if (QMode == 1) {
ObjectWaiter * s = NULL ;
ObjectWaiter * t = w ;
ObjectWaiter * u = NULL ;
while (t != NULL) {// _cxq不为空
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 ;// 通过上面while将_cxq的元素转为FIFO队列并赋值给_EntryList,也就是完成了迁移逻辑
assert (s != NULL, "invariant") ;
} else {
// 执行Omode==0或者Omode==2的逻辑
// 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 ;
}
}
if (_succ != NULL) continue;
w = _EntryList ;
// 最后释放_EntryList中的节点
if (w != NULL) {
guarantee (w->TState == ObjectWaiter::TS_ENTER, "invariant") ;
ExitEpilog (Self, w) ;
return ;
}
}
}
至此exit的已经完全分析完了,这里的核心是要和enter的_cxq队列相互对应,然后再结合 _EntryList队列来看。
核心逻辑就是这OMode4中模式,这4中模式分别对应了唤醒线程的优先级,是先唤醒在持有锁期间被阻塞的线程(也就是_cxq优先)还是先唤醒已经阻塞了的线程( _EntryList优先),并且还涉及了唤醒的优先级是队头还是队尾。只要搞清楚了这个,就不难估算synchronized的释放的时候唤醒线程概率。
总结:
看了上面源码,我们就该回答得上下面加个问题?
1、synchronized加锁主要进行了什么操作?
自适应自旋+CAS替换_Owner获取锁,使用park(mutex)阻塞线程
2、_cxq队列和 _EntryList队列的关系,它们之间是怎样合作的?
_cxq队列是在锁被持有期间,竞争失败的线程存放的队列, _EntryList是在锁抢占之后失败存放的地方,可以理解 _EntryList存放的是唤醒后竞争失败的线程。具体是怎么工作的可看EnterI里面的注释和exit的锁释放策略分析注释逻辑。
3、线程释放,默认最先唤醒的是哪一个线程,这个怎么判断?
这个要根据释放策略QModel来分析,默认为0,则默认是最先释放在持有锁期间,最后一个阻塞的线程。是后进先出(LIFO)策略。注意,这里结论是在持有阻塞唤醒的情况下并不是抢占情况下,抢占情况下是非公平的,谁抢到就是谁的。
这里的结论例子如:Thread1持有锁,Thread2,3,4都来竞争锁,并且都竞争失败进入了阻塞,此时一直到Thread1释放锁之前都没有线程竞争锁,则唤醒的就是Thread4。
代码演示:
这段代码是我在网上扒的,但是可以很好的演示出结论:
public static void main(String[] args) { SynchronizedExitDemo lock = new SynchronizedExitDemo(); Thread t3 = new Thread(() -> { System.out.println("Thread 3 start!!!!!!"); synchronized (lock) { try { System.in.read(); } catch (Exception e) { } System.out.println("Thread 3 end!!!!!!"); } }); Thread t4 = new Thread(() -> { System.out.println("Thread 4 start!!!!!!"); synchronized (lock) { System.out.println("Thread 4 end!!!!!!"); } }); Thread t5 = new Thread(() -> { int a = 0; System.out.println("Thread 5 start!!!!!!"); synchronized (lock) { a++; System.out.println("Thread 5 end!!!!!!"); } }); Thread t6 = new Thread(() -> { int a = 0; System.out.println("Thread 6 start!!!!!!"); synchronized (lock) { a++; System.out.println("Thread 6 end!!!!!!"); } }); t3.start(); try { TimeUnit.SECONDS.sleep(1); } catch (Exception e) { } t4.start(); try { TimeUnit.SECONDS.sleep(1); } catch (Exception e) { } t5.start(); try { TimeUnit.SECONDS.sleep(1); } catch (Exception e) { } t6.start(); } /** Thread 3 start!!!!!! Thread 4 start!!!!!! Thread 5 start!!!!!! Thread 6 start!!!!!! 1 Thread 3 end!!!!!! Thread 6 end!!!!!! Thread 5 end!!!!!! Thread 4 end!!!!!! **/首先t3启动,并持有锁,随后t4,5,6相继启动,这时候4,5,6都因为竞争t3持有锁,导致竞争锁失败,因此都陷入了等待,等待顺序为启动顺序的待续6->5->4,此时synchronized内部的_cxq队列就该这样。然后在键盘上输入一个值,释放t3持有锁,然后开始唤醒,发现唤醒顺序正式6->6->4。因此证明默认唤醒顺序为后进先出(LIFO)策略。
ps:至此synchronized的基本流程和源码细节分析完毕,后续还有wait和notify等没有分析,有时间再分析吧。读源码分析,并不是去学习人家的编码,不必去抠得太细,里面还涉及很多位运算等。而是学习其场景解决方案和思路,比如读synchronized的源码就能和AQS实现找到一些共性。