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中分配一批monitoromFreeList中。

2.初始化monitor对象

3.将状态设置为膨胀中(INFLATING)状态

4.设置monitorheader字段为displaced mark wordowner字段为Lock Recordobj字段为锁对象

5.设置锁对象头的mark word为重量级锁状态,指向第一步分配的monitor对象

无锁状态下的膨胀流程如下:

1.调用omAlloc分配一个ObjectMonitor对象(以下简称monitor)

2.初始化monitor对象

3.设置monitor的header字段为mark word,owner字段为null,obj字段为锁对象

4.设置锁对象头的mark word为重量级锁状态,指向第一步分配的monitor对象

  这也说明 如果一个轻量级锁膨胀到重量级锁,那么还是它拿着锁。
  同时注意一个ObjectMonitor 有三个重要的属性 header, owner, obj,这里owner更是
  
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 ;  
} 

 

   现在知道Monitor和对象是怎么关联的了吧,对象头会指向monitor
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 ;
  }
  1. 如果当前是无锁状态、锁重入、当前线程是之前持有轻量级锁的线程则进行简单操作后返回。
  2. 先自旋尝试获得锁,这样做的目的是为了减少执行操作系统同步操作带来的开销
  3. 调用EnterI方法获得锁或阻塞

  至于重量级的逻辑,我就直接照搬作者的原图说明下,不再贴代码,因为这部分和AQS真的很像

  一个ObjectMonitor对象包括这么几个关键字段:cxq(下图中的ContentionList),EntryList ,WaitSet,owner。

  其中cxq ,EntryList ,WaitSet都是由ObjectWaiter的链表结构,owner指向持有锁的线程。

  
 
  
  当一个线程尝试获得锁时,如果该锁已经被占用,则会将该线程封装成一个ObjectWaiter对象插入到cxq的队列的队首,然后调用park函数挂起当前线程。在linux系统上,park函数底层调用的是gclib库的pthread_cond_waitJDK的ReentrantLock底层也是用该方法挂起线程的。这句话太重要了。

  当线程释放锁时,会从cxq或EntryList中挑选一个线程唤醒,被选中的线程叫做Heir presumptive即假定继承人(应该是这样翻译),就是图中的Ready Thread,假定继承人被唤醒后会尝试获得锁,但synchronized是非公平的,所以假定继承人不一定能获得锁(这也是它叫"假定"继承人的原因)。

  如果线程获得锁后调用Object#wait方法,则会将线程加入到WaitSet中,当被Object#notify唤醒后,会将线程从WaitSet移动到cxq或EntryList中去。需要注意的是,当调用一个锁对象的waitnotify方法时,如当前锁的状态是偏向锁或轻量级锁则会先膨胀成重量级锁

  synchronizedmonitor锁机制和JDK的ReentrantLockCondition是很相似的,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块中显示释放锁;

posted on 2020-10-30 18:21  MaXianZhe  阅读(568)  评论(0)    收藏  举报

导航