synchronized_4_锁膨胀重量级锁

155 阅读19分钟

锁膨胀与重量级锁

介绍

锁膨胀主要发生在竞争较大的情况下,发生在通过轻量级手段无法获取到目标锁的时候,就会执行锁膨胀,锁膨胀的目标是为了实现重量级锁相关操作。

锁膨胀的主要功能是返回线程与锁关联的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锁的相关源码中逻辑算是比较简单的

具体流程如下:

image-20220524203723708.png

重量级锁之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链表

大致的流程如下:

image-20220508112830850-16519805128251.png

源码分析

源码分析主要还是从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实现找到一些共性。