Java多线程-对象内置锁(ObjectMonitor)
1、介绍
Monitor是在JVM层对Java并发控制synchronized的重量级锁的实现。通过ObjectMonitor来实现并发的锁控制。同时也是Java基础对象Object的wait,nofity方法的底层支持实现。
2、对象模型
2.1 数据结构图
ObjectMonitor整体上可以分为两部分,一部分是是这个监控对象的基本信息表示当前锁的实时状态,一部分表示各种情况下需要获取锁的排队信息。如下图所示:
基本信息
1 header
用来保存锁对象的mark word的值。因为object里面已经不保存mark word的原来的值了,保存的是ObjectMonitor对象的地址信息。当所有线程都完成了之后,需要销毁掉ObjectMonitor的时候需要将原有的header里面的值重新复制到mark word中来。
2 object
指向的是对象的地址信息,方便通过ObjectMonitor来访问对应的锁对象。
3 owner
指向的是当前获得线程的地址,用来判断当前锁是被哪个线程持有。
排队信息
1cxq
线程刚进来的时候没有获取到锁的时候,在当前的排队队列。
2waitSet
是指已经获取得一次锁了,对象调用了wait方法,讲当前线程挂起了就进入了等待队列。等待时间到期的时候唤醒,或者其他线程唤醒。
3 entryList
是队列用来获取锁的缓冲区,用来将cxq和waitSet中的数据 移动到entryList进行排队。这个统一获取锁的入口。一般是cxq 或者waitSet数据复制过来进行统一排队。
2.2 ObjectMonitor 的源代码
ObjectMonitor() { _header = NULL; _count = 0; _waiters = 0, _recursions = 0; _object = NULL; _owner = NULL; _WaitSet = NULL; _WaitSetLock = 0 ; _Responsible = NULL ; _succ = NULL ; //多线程竞争锁进入时的单向链表 _cxq = NULL ; FreeNext = NULL ; //_owner从该双向循环链表中唤醒线程结点,_EntryList是第一个节点 _EntryList = NULL ; _SpinFreq = 0 ; _SpinClock = 0 ; OwnerIsThread = 0 ; _previous_owner_tid = 0; }
2.3 ObjectWaiter源代码
下面是排队对象ObjectWaiter的源代码的信息
class ObjectWaiter : public StackObj { public: enum TStates { TS_UNDEF, TS_READY, TS_RUN, TS_WAIT, TS_ENTER, TS_CXQ } ; enum Sorted { PREPEND, APPEND, SORTED } ; ObjectWaiter * volatile _next; ObjectWaiter * volatile _prev; //等待的线程 Thread* _thread; jlong _notifier_tid; ParkEvent * _event; volatile int _notified ; volatile TStates TState ; Sorted _Sorted ; // List placement disposition bool _active ; // Contention monitoring is enabled };
3、核心方法
3.1 TryLock
int ObjectMonitor::TryLock (Thread * Self) { for (;;) { void * own = _owner ; //已经有线程获取到锁了 直接返回 if (own != NULL){ return 0 ; } //获取锁成功 if (Atomic::cmpxchg_ptr (Self, &_owner, NULL) == NULL) { return 1 ; } if (true) return -1 ; } }
通过上面代码我们可以详细的获取锁的逻辑,判断当前的监视器对象是否已经为空了,如果不为空说明已经有线程拿到锁了直接结束。当为空闲的尝试设置当前线程来获取锁,成功了直接返回1 。通过上面代码看总体上只做一次尝试,并不会多次尝试。
3.3 try_enter
try_enter 的方法是尝试获取当前锁对象 ,在已经拿过一次锁对象了之后,二次重新进入锁的场景使用。整个过程是只做一次尝试,不论成功失败都只执行一次。
bool ObjectMonitor::try_enter(Thread* THREAD) {
if (THREAD != _owner) {
//判断当前线程是否已经活动锁
if (THREAD->is_lock_owned ((address)_owner)) {
_owner = THREAD ;
_recursions = 1 ;
OwnerIsThread = 1 ;
return true;
}
//设置当前线程为当前线程
if (Atomic::cmpxchg_ptr (THREAD, &_owner, NULL) != NULL) {
return false;
}
return true;
} else {
_recursions++;
return true;
}
}
通过上述逻辑我们可以看到当前的线程是否已经持有了这个锁对象,如果持有了直接返回,如果没有持有尝试持有下。
3.3 enter
enter方法是整个获取到锁逻辑的核心实现,核心的思想是在整个链路中尽一切可能的获取到锁,如果实在获取不到情况进入等待队列进行等待。整体思路如下:
1 尝试获取获取获取锁,获取到成功直接返回。这个时刻整个等待成不是最低的。
2 判断自己是否已经拿到锁了,如果前面当前线程已经获取到锁了,也就直接返回。从这这里看ObjectMonitor是可以重入的。
3 通过自旋多次尝试获取到锁的信息,如果获取到直接返回。满满的求生生欲望,避免自己加入到等待。
4 通过自旋转调用EnterI方法让自己加入到cxq等待队列。一旦调用成功了整个线程就是park了,交出了执行状态等待唤醒了。
5 整个获取到锁之后就调用exit推出了。
void ATTR ObjectMonitor::enter(TRAPS) { //定义当前线程变量 Thread * const Self = THREAD ; void * cur ; //1 如果没有人持有这个monitor 直接将自己设置成持有者 cur = Atomic::cmpxchg_ptr (Self, &_owner, NULL) ; if (cur == NULL) { return ; } //2 如果已经持有了 计数+1 if (cur == Self) { _recursions ++ ; return ; } //判断当前线程的地址 if (Self->is_lock_owned ((address)cur)) { _recursions = 1 ; _owner = Self ; OwnerIsThread = 1 ; return ; } Self->_Stalled = intptr_t(this) ; //3 如果允许自旋,通过自旋转来获取锁的信息 if (Knob_SpinEarly && TrySpin (Self) > 0) { assert (((oop)(object()))->mark() == markOopDesc::encode(this), "invariant") ; Self->_Stalled = 0 ; return ; } //前面的尝试都失败了就开始正式的进入到等待队列进行等待了 JavaThread * jt = (JavaThread *) Self ; //增加等待计数 Atomic::inc_ptr(&_count); { //4 自旋进入到等待队列中来 for (;;) { jt->set_suspend_equivalent(); //重点是加入等待队列中来 EnterI (THREAD) ; _recursions = 0 ; _succ = NULL ; //5 推出机制 exit (false, Self) ; jt->java_suspend_self(); } Self->set_current_pending_monitor(NULL); } //减少等待计数 Atomic::dec_ptr(&_count); Self->_Stalled = 0 ; }
3.4 EnterI
EnterI 是整个进入等待队列的核心实现,核心的思想是在尽最后可能的获取到锁,如果实在不行加入到_cxq的等待队列,加入队列成功后将当前线程挂起。整体思路如下:
1 尝试通过TryLock尝试获取下对象锁的信息。
2 尝试通过自旋锁来获取锁的信息,这也是最后一次尝试了。实在不行就只能等待了。
3 构造等待节点对象,开始进行等待了。
4 将当前线程的等待对象加入到能对队列_cxq排队队列中来
5 每个执行的循环周期都会经历尝试获取锁,获取不到将自己挂起park的过程。中间有个小插曲尝试将自己设置成整个对象的维护线程(_Responsiblew),是防止获取锁的所有线程都进入了等待队列,没有人能通知其他线程进行拿锁的操作。
void ATTR ObjectMonitor::EnterI (TRAPS) { Thread * Self = THREAD ; //1 尝试获取当前的锁对象 if (TryLock (Self) > 0) { return ; } DeferredInitialize () ; //2 尝试通过自旋获取到锁的机制 if (TrySpin (Self) > 0) { return ; } //3 构造一个 ObjectWaiter对象 也就是等待对象 ObjectWaiter node(Self) ; Self->_ParkEvent->reset() ; node._prev = (ObjectWaiter *) 0xBAD ; node.TState = ObjectWaiter::TS_CXQ ; //4 加入到能对队列_cxq中来 ObjectWaiter * nxt ; for (;;) { node._next = nxt = _cxq ; //添加完成直接返回 if (Atomic::cmpxchg_ptr (&node, &_cxq, nxt) == nxt) break ; //顺便捎带加入下锁 看看是否成功 if (TryLock (Self) > 0) { return ; } } //如果没有其他线程 将当前线程设置成负责线程 以方便后面对象回收 if ((SyncFlags & 16) == 0 && nxt == NULL && _EntryList == NULL) { Atomic::cmpxchg_ptr (Self, &_Responsible, NULL) ; } int nWakeups = 0 ; int RecheckInterval = 1 ; //5 循环进入等待和唤醒的情况,这个代码需要仔细研读 for (;;) { //尝试加个锁看看是否有运气 if (TryLock (Self) > 0) break ; if ((SyncFlags & 2) && _Responsible == NULL) { Atomic::cmpxchg_ptr (Self, &_Responsible, NULL) ; } //防止所有的线程都进入死等的情况,这样就会出现线程死锁 if (_Responsible == Self || (SyncFlags & 1)) { //过一段时间就起来看看 Self->_ParkEvent->park ((jlong) RecheckInterval) ; RecheckInterval *= 8 ; if (RecheckInterval > 1000) RecheckInterval = 1000 ; } else { Self->_ParkEvent->park() ; } //尝试加个锁看看是否有运气 if (TryLock(Self) > 0) break ; ++ 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 ; } //已经获取到了锁,讲当前节点从等待队列中解除 UnlinkAfterAcquire (Self, &node) ; return ; }
3.5 exit
线程执行同步方法或代码块结束之后,会调用exit方法将当前线程从锁对象中移除,并尝试唤醒启动的线程来获取锁的过程。核心要点有两个,一个是将当前线程释放,这个很好理解就把_owner字段中线程值设为空就好了。另一个是唤醒其他线程,这个就涉及到相关的策略,因为前面他有一个排队队列_cxq,还有一个_EntryList,到底从哪里优先去取。提供了四种策略:
QMode=2 的时候 优先从_cxq唤醒,执行过程如下图:
QMode=3 的时候 _cxq中的数据加入到_EntryList尾部中来 然后从_EntryList开始获取
QMode=4的时候 _cxq中的数据加入到_EntryList前面来 然后从_EntryList开始获取
QMode=1 的时候 这个是默认策略 优先从_EntryList 中获取 如果_EntryList为空的情况,把_cxq进行队列反转然后从_cxq获取
整个策略本质上是考虑公平性与吞吐效率的考量。
void ATTR ObjectMonitor::exit(bool not_suspended, TRAPS) { Thread * Self = THREAD ; //1判断当前线程是否是锁的线程 if (THREAD != _owner) { if (THREAD->is_lock_owned((address) _owner)) { _owner = THREAD ; _recursions = 0 ; OwnerIsThread = 1 ; } } //重入锁的次数减1 if (_recursions != 0) { _recursions--; // this is simple recursive enter 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 ; } for (;;) { if (Knob_ExitPolicy == 0) { OrderAccess::release_store_ptr (&_owner, NULL) ; // drop the lock OrderAccess::storeload() ; //没有人需要获取锁了直接返回 if ((intptr_t(_EntryList)|intptr_t(_cxq)) == 0 || _succ != NULL) { return ; } //当锁已经被其他线程抢占了 直接推出就好了 if (Atomic::cmpxchg_ptr (THREAD, &_owner, NULL) != NULL) { return ; } } else { if ((intptr_t(_EntryList)|intptr_t(_cxq)) == 0 || _succ != NULL) { OrderAccess::release_store_ptr (&_owner, NULL) ; // drop the lock OrderAccess::storeload() ; if (_cxq == NULL || _succ != NULL) { return ; } if (Atomic::cmpxchg_ptr (THREAD, &_owner, NULL) != NULL) { return ; } } } ObjectWaiter * w = NULL ; int QMode = Knob_QMode ; //当QMode=2的时候 优先从_cxq唤醒 if (QMode == 2 && _cxq != NULL) { w = _cxq ; assert (w != NULL, "invariant") ; assert (w->TState == ObjectWaiter::TS_CXQ, "Invariant") ; ExitEpilog (Self, w) ; return ; } //当QMode=3的时候 讲_cxq中的数据加入到_EntryList尾部中来 然后从_EntryList开始获取 if (QMode == 3 && _cxq != NULL) { w = _cxq ; for (;;) { 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 ; } //讲_cxq数据加入到_EntryList的队列中来 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 ; } } //当QMode=4的时候 讲_cxq中的数据加入到_EntryList前面来 然后从_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 ; } //批量修改状态标志改成TS_ENTER ObjectWaiter * q = NULL ; ObjectWaiter * p ; for (p = w ; p != NULL ; p = p->_next) { p->TState = ObjectWaiter::TS_ENTER ; p->_prev = q ; q = p ; } //插到原有的_EntryList前面 从员_EntryList中获取 if (_EntryList != NULL) { q->_next = _EntryList ; _EntryList->_prev = q ; } _EntryList = w ; // Fall thru into code that tries to wake a successor from EntryList } w = _EntryList ; if (w != NULL) { assert (w->TState == ObjectWaiter::TS_ENTER, "invariant") ; ExitEpilog (Self, w) ; return ; } // 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 ; //默认的策略 for (;;) { assert (w != NULL, "Invariant") ; ObjectWaiter * u = (ObjectWaiter *) Atomic::cmpxchg_ptr (NULL, &_cxq, w) ; if (u == w) break ; w = u ; } //将cxq排队队列进行翻转。 if (QMode == 1) { // 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 { // 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 ; if (w != NULL) { guarantee (w->TState == ObjectWaiter::TS_ENTER, "invariant") ; ExitEpilog (Self, w) ; return ; } } }
在HotSpot虚拟机中,Monitor是基于C++的ObjectMonitor类实现的,其主要成员包括:
- _owner:指向持有ObjectMonitor对象的线程
- _WaitSet:存放处于wait状态的线程队列,即调用wait()方法的线程
- _EntryList:存放处于等待锁block状态的线程队列
- _count:约为_WaitSet 和 _EntryList 的节点数之和
- _cxq: 多个线程争抢锁,会先存入这个单向链表
- _recursions: 记录重入次数
上图简略展示了ObjectMonitor的基本工作机制:
(1)当多个线程同时访问一段同步代码时,首先会进入 _EntryList 队列中。
(2)当某个线程获取到对象的Monitor后进入临界区域,并把Monitor中的 _owner 变量设置为当前线程,同时Monitor中的计数器 _count 加1。即获得对象锁。
(3)若持有Monitor的线程调用 wait() 方法,将释放当前持有的Monitor,_owner变量恢复为null,_count自减1,同时该线程进入 _WaitSet 集合中等待被唤醒。
(4)在_WaitSet 集合中的线程会被再次放到_EntryList 队列中,重新竞争获取锁。
(5)若当前线程执行完毕也将释放Monitor并复位变量的值,以便其他线程进入获取锁。
线程争抢锁的过程要比上面展示得更加复杂。除了_EntryList 这个双向链表用来保存竞争的线程,ObjectMonitor中还有另外一个单向链表 _cxq,由两个队列来共同管理并发的线程。
ObjectMonitor::enter() 和 ObjectMonitor::exit() 分别是ObjectMonitor获取锁和释放锁的方法。线程解锁后还会唤醒之前等待的线程,根据策略选择直接唤醒_cxq队列中的头部线程去竞争,或者将_cxq队列中的线程加入_EntryList,然后再唤醒_EntryList队列中的线程去竞争。
ObjectMonitor::enter()
下面我们看一下ObjectMonitor::enter()方法竞争锁的流程:
首先尝试通过 CAS 把 ObjectMonitor 中的 _owner 设置为当前线程,设置成功就表示获取锁成功。通过 _recursions 的自增来表示重入。
如果没有CAS成功,那么就开始启动自适应自旋,自旋还不行的话,就包装成 ObjectWaiter 对象加入到 _cxq 单向链表之中。关于自旋锁和自适应自旋,可以参考前文《Java面试必考问题:什么是自旋锁 》](https://www.toutiao.com/i6934327407897854475/?group_id=6934327407897854475)。
加入_cxq链表后,再次尝试是否可以CAS拿到锁,再次失败就要阻塞(block),底层调用了pthread_mutex_lock。
ObjectMonitor::exit()方法
线程执行 Object.wait()方法时,会将当前线程加入到 _waitSet 这个双向链表中,然后再运行ObjectMonitor::exit() 方法来释放锁。
可重入锁就是根据 _recursions 来判断的,重入一次就执行 _recursions++,解锁一次就执行 _recursions–,如果 _recursions 减到 0 ,就说明需要释放锁了。
线程解锁后还会唤醒之前等待的线程。当线程执行 Object.notify()方法时,从 _waitSet 头部拿线程节点,然后根据策略(QMode指定)决定将线程节点放在哪里,包括_cxq 或 _EntryList 的头部或者尾部,然后唤醒队列中的线程。