synchronized 之重量级锁
本文来自于https://www.jianshu.com/p/09de11d71ef8 请大佬收下我的膝盖
轻量级锁膨胀
ObjectMonitor * ATTR ObjectSynchronizer::inflate (Thread * Self, oop object) { ... for (;;) { const markOop mark = object->mark() ; assert (!mark->has_bias_pattern(), "invariant") ; // mark是以下状态中的一种: // * Inflated(重量级锁状态) - 直接返回 // * Stack-locked(轻量级锁状态) - 膨胀 // * INFLATING(膨胀中) - 忙等待直到膨胀完成 // * Neutral(无锁状态) - 膨胀 // * BIASED(偏向锁) - 非法状态,在这里不会出现 // CASE: inflated if (mark->has_monitor()) { // 已经是重量级锁状态了,直接返回 ObjectMonitor * inf = mark->monitor() ; ... return inf ; } // CASE: inflation in progress if (mark == markOopDesc::INFLATING()) { // 正在膨胀中,说明另一个线程正在进行锁膨胀,continue重试 TEVENT (Inflate: spin while INFLATING) ; // 在该方法中会进行spin/yield/park等操作完成自旋动作 ReadStableMark(object) ; continue ; } if (mark->has_locker()) { // 当前轻量级锁状态,先分配一个ObjectMonitor对象,并初始化值 ObjectMonitor * m = omAlloc (Self) ; m->Recycle(); m->_Responsible = NULL ; m->OwnerIsThread = 0 ; m->_recursions = 0 ; m->_SpinDuration = ObjectMonitor::Knob_SpinLimit ; // Consider: maintain by type/class // 将锁对象的mark word设置为INFLATING (0)状态 markOop cmp = (markOop) Atomic::cmpxchg_ptr (markOopDesc::INFLATING(), object->mark_addr(), mark) ; if (cmp != mark) { omRelease (Self, m, true) ; continue ; // Interference -- just retry } // 栈中的displaced mark word markOop dmw = mark->displaced_mark_helper() ; assert (dmw->is_neutral(), "invariant") ; // 设置monitor的字段 m->set_header(dmw) ; // owner为Lock Record m->set_owner(mark->locker()); m->set_object(object); ... // 将锁对象头设置为重量级锁状态 object->release_set_mark(markOopDesc::encode(m)); ... return m ; } // CASE: neutral // 分配以及初始化ObjectMonitor对象 ObjectMonitor * m = omAlloc (Self) ; // prepare m for installation - set monitor to initial state m->Recycle(); m->set_header(mark); // owner为NULL 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 // 用CAS替换对象头的mark word为重量级锁状态 if (Atomic::cmpxchg_ptr (markOopDesc::encode(m), object->mark_addr(), mark) != mark) { // 不成功说明有另外一个线程在执行inflate,释放monitor对象 m->set_object (NULL) ; m->set_owner (NULL) ; m->OwnerIsThread = 0 ; m->Recycle() ; omRelease (Self, m, true) ; m = NULL ; continue ; // interference - the markword changed - just retry. // The state-transitions are one-way, so there's no chance of // live-lock -- "Inflated" is an absorbing state. } ... return m ; } }
inflate中是一个for循环,主要是为了处理多线程同时调用inflate的情况。然后会根据锁对象的状态进行不同的处理:
1.已经是重量级状态,说明膨胀已经完成,直接返回
2.如果是轻量级锁则需要进行膨胀操作
3.如果是膨胀中状态,则进行忙等待
4.如果是无锁状态则需要进行膨胀操作
其中轻量级锁和无锁状态需要进行膨胀操作,轻量级锁膨胀流程如下:
1.调用omAlloc分配一个ObjectMonitor对象(以下简称monitor),在omAlloc方法中会先从线程私有的monitor集合omFreeList中分配对象,如果omFreeList中已经没有monitor对象,则从JVM全局的gFreeList中分配一批monitor到omFreeList中。
2.初始化monitor对象
3.将状态设置为膨胀中(INFLATING)状态
4.设置monitor的header字段为displaced mark word,owner字段为Lock Record,obj字段为锁对象
5.设置锁对象头的mark word为重量级锁状态,指向第一步分配的monitor对象
无锁状态下的膨胀流程如下:
1.调用omAlloc分配一个ObjectMonitor对象(以下简称monitor)
2.初始化monitor对象
3.设置monitor的header字段为mark word,owner字段为null,obj字段为锁对象
4.设置锁对象头的mark word为重量级锁状态,指向第一步分配的monitor对象
ObjectMonitor::ObjectMonitor() { _header = NULL; _count = 0; _waiters = 0, _recursions = 0; //线程的重入次数 _object = NULL; _owner = NULL; //标识拥有该monitor的线程 _WaitSet = NULL; //等待线程组成的双向循环链表,_WaitSet是第一个节点 _WaitSetLock = 0 ; _Responsible = NULL ; _succ = NULL ; _cxq = NULL ; //多线程竞争锁进入时的单向链表 FreeNext = NULL ; _EntryList = NULL ; //_owner从该双向循环链表中唤���线程结点,_EntryList是第一个节点 _SpinFreq = 0 ; _SpinClock = 0 ; OwnerIsThread = 0 ; }
Atomic::cmpxchg_ptr (markOopDesc::encode(m), object->mark_addr(), mark)
同时呢,monitor的owner迟早都会指向持有这把锁的线程,锁对象,Monitor和线程就这么关联起来了
同时结合一张很经典的图,重量级锁的时候指向的是互斥量的指针,现在我们知道了吧,所谓的互斥量就是monitor

// 栈中的displaced mark word markOop dmw = mark->displaced_mark_helper() ; 我懵逼了 为啥栈中的 displaced mark word 是通过mark得到的呢,难道是mark word指向lock record?
// 设置monitor的字段
m->set_header(dmw) ;
// owner为Lock Record
m->set_owner(mark->locker());
m->set_object(object);
重量级锁竞争
void ATTR ObjectMonitor::enter(TRAPS) { Thread * const Self = THREAD ; void * cur ; // owner为null代表无锁状态,如果能CAS设置成功,则当前线程直接获得锁 cur = Atomic::cmpxchg_ptr (Self, &_owner, NULL) ; if (cur == NULL) { ... return ; } // 如果是重入的情况 if (cur == Self) { // TODO-FIXME: check for integer overflow! BUGID 6557169. _recursions ++ ; return ; } // 当前线程是之前持有轻量级锁的线程。由轻量级锁膨胀且第一次调用enter方法,那cur是指向Lock Record的指针 if (Self->is_lock_owned ((address)cur)) { assert (_recursions == 0, "internal state error"); // 重入计数重置为1 _recursions = 1 ; // 设置owner字段为当前线程(之前owner是指向Lock Record的指针) _owner = Self ; OwnerIsThread = 1 ; return ; } ... // 在调用系统的同步操作之前,先尝试自旋获得锁 if (Knob_SpinEarly && TrySpin (Self) > 0) { ... //自旋的过程中获得了锁,则直接返回 Self->_Stalled = 0 ; return ; } ... { ... for (;;) { jt->set_suspend_equivalent(); // 在该方法中调用系统同步操作 EnterI (THREAD) ; ... } Self->set_current_pending_monitor(NULL); } ... }
无锁状态的竞争
竞争的是什么呢
cur = Atomic::cmpxchg_ptr (Self, &_owner, NULL) ; 这里命名就是把owner设置为Self这个线程,为啥上面说的是lock record呢 我懵逼
这也能解释重量级锁竞争是要竞争写Monitor ,可不是网上瞎说的写mark word里的指针,跟那个一点关系没有了。
看到了吧,自旋是在重量级锁里的,根本不是轻量级锁里的,网上那帮人哟,误人子弟包括马士兵的公开课也说的是轻量级锁会自旋
if (Knob_SpinEarly && TrySpin (Self) > 0) { ... //自旋的过程中获得了锁,则直接返回 Self->_Stalled = 0 ; return ; }
还有一个地方让我在意,作者是这么说的owner之前指向Lock Record,当时是锁膨胀还未竞争,当开始重量级锁竞争后,owner不再指向lock record,而是直接指向线程本身的地址
// 当前线程是之前持有轻量级锁的线程。由轻量级锁膨胀且第一次调用enter方法,那cur是指向Lock Record的指针 if (Self->is_lock_owned ((address)cur)) { assert (_recursions == 0, "internal state error"); // 重入计数重置为1 _recursions = 1 ; // 设置owner字段为当前线程(之前owner是指向Lock Record的指针) _owner = Self ; OwnerIsThread = 1 ; return ; }
- 如果当前是无锁状态、锁重入、当前线程是之前持有轻量级锁的线程则进行简单操作后返回。
- 先自旋尝试获得锁,这样做的目的是为了减少执行操作系统同步操作带来的开销
- 调用
EnterI方法获得锁或阻塞
至于重量级的逻辑,我就直接照搬作者的原图说明下,不再贴代码,因为这部分和AQS真的很像
一个ObjectMonitor对象包括这么几个关键字段:cxq(下图中的ContentionList),EntryList ,WaitSet,owner。
其中cxq ,EntryList ,WaitSet都是由ObjectWaiter的链表结构,owner指向持有锁的线程。

ObjectWaiter对象插入到cxq的队列的队首,然后调用park函数挂起当前线程。在linux系统上,park函数底层调用的是gclib库的pthread_cond_wait,JDK的ReentrantLock底层也是用该方法挂起线程的。这句话太重要了。 当线程释放锁时,会从cxq或EntryList中挑选一个线程唤醒,被选中的线程叫做Heir presumptive即假定继承人(应该是这样翻译),就是图中的Ready Thread,假定继承人被唤醒后会尝试获得锁,但synchronized是非公平的,所以假定继承人不一定能获得锁(这也是它叫"假定"继承人的原因)。
如果线程获得锁后调用Object#wait方法,则会将线程加入到WaitSet中,当被Object#notify唤醒后,会将线程从WaitSet移动到cxq或EntryList中去。需要注意的是,当调用一个锁对象的wait或notify方法时,如当前锁的状态是偏向锁或轻量级锁则会先膨胀成重量级锁。
synchronized的monitor锁机制和JDK的ReentrantLock与Condition是很相似的,ReentrantLock也有一个存放等待获取锁线程的链表,Condition也有一个类似WaitSet的集合用来存放调用了await的线程。如果你之前对ReentrantLock有深入了解,那理解起monitor应该是很简单。
这里我总结下,尤其是红色字的部分,就是我们知道AQS里挂起一个线程用LockSupport.park这个方法有个牛逼的地方,就是不响应中断,也就是说跟sleep不同,一个线程被挂起后,调用interrupt它是没法返回的,除非有其他的线程调用unpark。既然synchronized和AQS都用同一个底层函数 pthread_cond_wait,这也能解释为啥 synchronized不能响应中断。
重量级锁的释放
重量级锁的释放源码相比获取要复杂好多,这里就不贴了,但是我可以说一下结论,那就是锁释放后还是回到无锁状态。
这个又和网上好多人讲的背道而驰,好多人说锁一旦升级就不会降级了,其实是不对的。
最后附上synchronized的几大特点
- Synchronized是非公平锁,ReentrantLock是可以是公平也可以是非公平的;
- Synchronized是不可以被中断的,而
ReentrantLock#lockInterruptibly方法是可以被中断的; - 在发生异常时Synchronized会自动释放锁(由javac编译时自动实现),而ReentrantLock需要开发者在finally块中显示释放锁;
浙公网安备 33010602011771号