死磕Synchronized底層實(shí)現(xiàn)--重量級(jí)鎖

本文為死磕Synchronized底層實(shí)現(xiàn)第三篇文章倔喂,內(nèi)容為重量級(jí)鎖實(shí)現(xiàn)。

本系列文章將對(duì)HotSpot的synchronized鎖實(shí)現(xiàn)進(jìn)行全面分析,內(nèi)容包括偏向鎖、輕量級(jí)鎖昆淡、重量級(jí)鎖的加鎖、解鎖刽严、鎖升級(jí)流程的原理及源碼分析昂灵,希望給在研究synchronized路上的同學(xué)一些幫助。主要包括以下幾篇文章:

死磕Synchronized底層實(shí)現(xiàn)--概論

死磕Synchronized底層實(shí)現(xiàn)--偏向鎖

死磕Synchronized底層實(shí)現(xiàn)--輕量級(jí)鎖

死磕Synchronized底層實(shí)現(xiàn)--重量級(jí)鎖

更多文章見個(gè)人博客:https://github.com/farmerjohngit/myblog

重量級(jí)的膨脹和加鎖流程

當(dāng)出現(xiàn)多個(gè)線程同時(shí)競(jìng)爭(zhēng)鎖時(shí)舞萄,會(huì)進(jìn)入到synchronizer.cpp#slow_enter方法

void ObjectSynchronizer::slow_enter(Handle obj, BasicLock* lock, TRAPS) {
  markOop mark = obj->mark();
  assert(!mark->has_bias_pattern(), "should not see bias pattern here");
  // 如果是無鎖狀態(tài)
  if (mark->is_neutral()) {
    lock->set_displaced_header(mark);
    if (mark == (markOop) Atomic::cmpxchg_ptr(lock, obj()->mark_addr(), mark)) {
      TEVENT (slow_enter: release stacklock) ;
      return ;
    }
    // Fall through to inflate() ...
  } else
  // 如果是輕量級(jí)鎖重入
  if (mark->has_locker() && THREAD->is_lock_owned((address)mark->locker())) {
    assert(lock != mark->locker(), "must not re-lock the same lock");
    assert(lock != (BasicLock*)obj->mark(), "don't relock with same BasicLock");
    lock->set_displaced_header(NULL);
    return;
  }

 ...
 

  // 這時(shí)候需要膨脹為重量級(jí)鎖眨补,膨脹前,設(shè)置Displaced Mark Word為一個(gè)特殊值倒脓,代表該鎖正在用一個(gè)重量級(jí)鎖的monitor
  lock->set_displaced_header(markOopDesc::unused_mark());
  //先調(diào)用inflate膨脹為重量級(jí)鎖撑螺,該方法返回一個(gè)ObjectMonitor對(duì)象,然后調(diào)用其enter方法
  ObjectSynchronizer::inflate(THREAD, obj())->enter(THREAD);
}

inflate中完成膨脹過程崎弃。

ObjectMonitor * ATTR ObjectSynchronizer::inflate (Thread * Self, oop object) {
  ...

  for (;;) {
      const markOop mark = object->mark() ;
      assert (!mark->has_bias_pattern(), "invariant") ;
    
      // mark是以下狀態(tài)中的一種:
      // *  Inflated(重量級(jí)鎖狀態(tài))     - 直接返回
      // *  Stack-locked(輕量級(jí)鎖狀態(tài)) - 膨脹
      // *  INFLATING(膨脹中)    - 忙等待直到膨脹完成
      // *  Neutral(無鎖狀態(tài))      - 膨脹
      // *  BIASED(偏向鎖)       - 非法狀態(tài)甘晤,在這里不會(huì)出現(xiàn)

      // CASE: inflated
      if (mark->has_monitor()) {
          // 已經(jīng)是重量級(jí)鎖狀態(tài)了,直接返回
          ObjectMonitor * inf = mark->monitor() ;
          ...
          return inf ;
      }

      // CASE: inflation in progress
      if (mark == markOopDesc::INFLATING()) {
         // 正在膨脹中饲做,說明另一個(gè)線程正在進(jìn)行鎖膨脹线婚,continue重試
         TEVENT (Inflate: spin while INFLATING) ;
         // 在該方法中會(huì)進(jìn)行spin/yield/park等操作完成自旋動(dòng)作 
         ReadStableMark(object) ;
         continue ;
      }
 
      if (mark->has_locker()) {
          // 當(dāng)前輕量級(jí)鎖狀態(tài),先分配一個(gè)ObjectMonitor對(duì)象盆均,并初始化值
          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
          // 將鎖對(duì)象的mark word設(shè)置為INFLATING (0)狀態(tài) 
          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") ;

          // 設(shè)置monitor的字段
          m->set_header(dmw) ;
          // owner為L(zhǎng)ock Record
          m->set_owner(mark->locker());
          m->set_object(object);
          ...
          // 將鎖對(duì)象頭設(shè)置為重量級(jí)鎖狀態(tài)
          object->release_set_mark(markOopDesc::encode(m));

         ...
          return m ;
      }

      // CASE: neutral
     
      // 分配以及初始化ObjectMonitor對(duì)象
      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替換對(duì)象頭的mark word為重量級(jí)鎖狀態(tài)
      if (Atomic::cmpxchg_ptr (markOopDesc::encode(m), object->mark_addr(), mark) != mark) {
          // 不成功說明有另外一個(gè)線程在執(zhí)行inflate塞弊,釋放monitor對(duì)象
          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中是一個(gè)for循環(huán),主要是為了處理多線程同時(shí)調(diào)用inflate的情況缀踪。然后會(huì)根據(jù)鎖對(duì)象的狀態(tài)進(jìn)行不同的處理:

1.已經(jīng)是重量級(jí)狀態(tài)居砖,說明膨脹已經(jīng)完成,直接返回

2.如果是輕量級(jí)鎖則需要進(jìn)行膨脹操作

3.如果是膨脹中狀態(tài)驴娃,則進(jìn)行忙等待

4.如果是無鎖狀態(tài)則需要進(jìn)行膨脹操作

其中輕量級(jí)鎖和無鎖狀態(tài)需要進(jìn)行膨脹操作奏候,輕量級(jí)鎖膨脹流程如下:

1.調(diào)用omAlloc分配一個(gè)ObjectMonitor對(duì)象(以下簡(jiǎn)稱monitor),在omAlloc方法中會(huì)先從線程私有的monitor集合omFreeList中分配對(duì)象唇敞,如果omFreeList中已經(jīng)沒有monitor對(duì)象蔗草,則從JVM全局的gFreeList中分配一批monitoromFreeList中。

2.初始化monitor對(duì)象

3.將狀態(tài)設(shè)置為膨脹中(INFLATING)狀態(tài)

4.設(shè)置monitor的header字段為displaced mark word疆柔,owner字段為Lock Record咒精,obj字段為鎖對(duì)象

5.設(shè)置鎖對(duì)象頭的mark word為重量級(jí)鎖狀態(tài),指向第一步分配的monitor對(duì)象

無鎖狀態(tài)下的膨脹流程如下:

1.調(diào)用omAlloc分配一個(gè)ObjectMonitor對(duì)象(以下簡(jiǎn)稱monitor)

2.初始化monitor對(duì)象

3.設(shè)置monitor的header字段為mark word旷档,owner字段為null模叙,obj字段為鎖對(duì)象

4.設(shè)置鎖對(duì)象頭的mark word為重量級(jí)鎖狀態(tài),指向第一步分配的monitor對(duì)象

至于為什么輕量級(jí)鎖需要一個(gè)膨脹中(INFLATING)狀態(tài)鞋屈,代碼中的注釋是:

// Why do we CAS a 0 into the mark-word instead of just CASing the
// mark-word from the stack-locked value directly to the new inflated state?
// Consider what happens when a thread unlocks a stack-locked object.
// It attempts to use CAS to swing the displaced header value from the
// on-stack basiclock back into the object header.  Recall also that the
// header value (hashcode, etc) can reside in (a) the object header, or
// (b) a displaced header associated with the stack-lock, or (c) a displaced
// header in an objectMonitor.  The inflate() routine must copy the header
// value from the basiclock on the owner's stack to the objectMonitor, all
// the while preserving the hashCode stability invariants.  If the owner
// decides to release the lock while the value is 0, the unlock will fail
// and control will eventually pass from slow_exit() to inflate.  The owner
// will then spin, waiting for the 0 value to disappear.   Put another way,
// the 0 causes the owner to stall if the owner happens to try to
// drop the lock (restoring the header from the basiclock to the object)
// while inflation is in-progress.  This protocol avoids races that might
// would otherwise permit hashCode values to change or "flicker" for an object.
// Critically, while object->mark is 0 mark->displaced_mark_helper() is stable.
// 0 serves as a "BUSY" inflate-in-progress indicator.

我沒太看懂范咨,有知道的同學(xué)可以指點(diǎn)下~

膨脹完成之后故觅,會(huì)調(diào)用enter方法獲得鎖

void ATTR ObjectMonitor::enter(TRAPS) {
   
  Thread * const Self = THREAD ;
  void * cur ;
  // owner為null代表無鎖狀態(tài),如果能CAS設(shè)置成功渠啊,則當(dāng)前線程直接獲得鎖
  cur = Atomic::cmpxchg_ptr (Self, &_owner, NULL) ;
  if (cur == NULL) {
     ...
     return ;
  }
  // 如果是重入的情況
  if (cur == Self) {
     // TODO-FIXME: check for integer overflow!  BUGID 6557169.
     _recursions ++ ;
     return ;
  }
  // 當(dāng)前線程是之前持有輕量級(jí)鎖的線程输吏。由輕量級(jí)鎖膨脹且第一次調(diào)用enter方法,那cur是指向Lock Record的指針
  if (Self->is_lock_owned ((address)cur)) {
    assert (_recursions == 0, "internal state error");
    // 重入計(jì)數(shù)重置為1
    _recursions = 1 ;
    // 設(shè)置owner字段為當(dāng)前線程(之前owner是指向Lock Record的指針)
    _owner = Self ;
    OwnerIsThread = 1 ;
    return ;
  }

  ...

  // 在調(diào)用系統(tǒng)的同步操作之前替蛉,先嘗試自旋獲得鎖
  if (Knob_SpinEarly && TrySpin (Self) > 0) {
     ...
     //自旋的過程中獲得了鎖贯溅,則直接返回
     Self->_Stalled = 0 ;
     return ;
  }

  ...

  { 
    ...

    for (;;) {
      jt->set_suspend_equivalent();
      // 在該方法中調(diào)用系統(tǒng)同步操作
      EnterI (THREAD) ;
      ...
    }
    Self->set_current_pending_monitor(NULL);
    
  }

  ...

}

  1. 如果當(dāng)前是無鎖狀態(tài)、鎖重入躲查、當(dāng)前線程是之前持有輕量級(jí)鎖的線程則進(jìn)行簡(jiǎn)單操作后返回它浅。
  2. 先自旋嘗試獲得鎖,這樣做的目的是為了減少執(zhí)行操作系統(tǒng)同步操作帶來的開銷
  3. 調(diào)用EnterI方法獲得鎖或阻塞

EnterI方法比較長(zhǎng)镣煮,在看之前罚缕,我們先闡述下其大致原理:

一個(gè)ObjectMonitor對(duì)象包括這么幾個(gè)關(guān)鍵字段:cxq(下圖中的ContentionList),EntryList 怎静,WaitSet,owner黔衡。

其中cxq 蚓聘,EntryList ,WaitSet都是由ObjectWaiter的鏈表結(jié)構(gòu)盟劫,owner指向持有鎖的線程夜牡。

sync-d.png

當(dāng)一個(gè)線程嘗試獲得鎖時(shí),如果該鎖已經(jīng)被占用侣签,則會(huì)將該線程封裝成一個(gè)ObjectWaiter對(duì)象插入到cxq的隊(duì)列的隊(duì)首塘装,然后調(diào)用park函數(shù)掛起當(dāng)前線程。在linux系統(tǒng)上影所,park函數(shù)底層調(diào)用的是gclib庫(kù)的pthread_cond_wait蹦肴,JDK的ReentrantLock底層也是用該方法掛起線程的。更多細(xì)節(jié)可以看我之前的兩篇文章:關(guān)于同步的一點(diǎn)思考-下猴娩,linux內(nèi)核級(jí)同步機(jī)制--futex

當(dāng)線程釋放鎖時(shí)阴幌,會(huì)從cxq或EntryList中挑選一個(gè)線程喚醒,被選中的線程叫做Heir presumptive即假定繼承人(應(yīng)該是這樣翻譯)卷中,就是圖中的Ready Thread矛双,假定繼承人被喚醒后會(huì)嘗試獲得鎖,但synchronized是非公平的蟆豫,所以假定繼承人不一定能獲得鎖(這也是它叫"假定"繼承人的原因)议忽。

如果線程獲得鎖后調(diào)用Object#wait方法,則會(huì)將線程加入到WaitSet中十减,當(dāng)被Object#notify喚醒后栈幸,會(huì)將線程從WaitSet移動(dòng)到cxq或EntryList中去愤估。需要注意的是,當(dāng)調(diào)用一個(gè)鎖對(duì)象的waitnotify方法時(shí)侦镇,如當(dāng)前鎖的狀態(tài)是偏向鎖或輕量級(jí)鎖則會(huì)先膨脹成重量級(jí)鎖灵疮。

synchronizedmonitor鎖機(jī)制和JDK的ReentrantLockCondition是很相似的,ReentrantLock也有一個(gè)存放等待獲取鎖線程的鏈表壳繁,Condition也有一個(gè)類似WaitSet的集合用來存放調(diào)用了await的線程震捣。如果你之前對(duì)ReentrantLock有深入了解,那理解起monitor應(yīng)該是很簡(jiǎn)單闹炉。

回到代碼上蒿赢,開始分析EnterI方法:

void ATTR ObjectMonitor::EnterI (TRAPS) {
    Thread * Self = THREAD ;
    ...
    // 嘗試獲得鎖
    if (TryLock (Self) > 0) {
        ...
        return ;
    }

    DeferredInitialize () ;
 
    // 自旋
    if (TrySpin (Self) > 0) {
        ...
        return ;
    }
    
    ...
    
    // 將線程封裝成node節(jié)點(diǎn)中
    ObjectWaiter node(Self) ;
    Self->_ParkEvent->reset() ;
    node._prev   = (ObjectWaiter *) 0xBAD ;
    node.TState  = ObjectWaiter::TS_CXQ ;

    // 將node節(jié)點(diǎn)插入到_cxq隊(duì)列的頭部,cxq是一個(gè)單向鏈表
    ObjectWaiter * nxt ;
    for (;;) {
        node._next = nxt = _cxq ;
        if (Atomic::cmpxchg_ptr (&node, &_cxq, nxt) == nxt) break ;

        // CAS失敗的話 再嘗試獲得鎖渣触,這樣可以降低插入到_cxq隊(duì)列的頻率
        if (TryLock (Self) > 0) {
            ...
            return ;
        }
    }

    // SyncFlags默認(rèn)為0羡棵,如果沒有其他等待的線程,則將_Responsible設(shè)置為自己
    if ((SyncFlags & 16) == 0 && nxt == NULL && _EntryList == NULL) {
        Atomic::cmpxchg_ptr (Self, &_Responsible, NULL) ;
    }


    TEVENT (Inflated enter - Contention) ;
    int nWakeups = 0 ;
    int RecheckInterval = 1 ;

    for (;;) {

        if (TryLock (Self) > 0) break ;
        assert (_owner != Self, "invariant") ;

        ...

        // park self
        if (_Responsible == Self || (SyncFlags & 1)) {
            // 當(dāng)前線程是_Responsible時(shí)嗅钻,調(diào)用的是帶時(shí)間參數(shù)的park
            TEVENT (Inflated enter - park TIMED) ;
            Self->_ParkEvent->park ((jlong) RecheckInterval) ;
            // Increase the RecheckInterval, but clamp the value.
            RecheckInterval *= 8 ;
            if (RecheckInterval > 1000) RecheckInterval = 1000 ;
        } else {
            //否則直接調(diào)用park掛起當(dāng)前線程
            TEVENT (Inflated enter - park UNTIMED) ;
            Self->_ParkEvent->park() ;
        }

        if (TryLock(Self) > 0) break ;

        ...
        
        if ((Knob_SpinAfterFutile & 1) && TrySpin (Self) > 0) break ;

        ...
        // 在釋放鎖時(shí)皂冰,_succ會(huì)被設(shè)置為EntryList或_cxq中的一個(gè)線程
        if (_succ == Self) _succ = NULL ;

        // Invariant: after clearing _succ a thread *must* retry _owner before parking.
        OrderAccess::fence() ;
    }

   // 走到這里說明已經(jīng)獲得鎖了

    assert (_owner == Self      , "invariant") ;
    assert (object() != NULL    , "invariant") ;
  
    // 將當(dāng)前線程的node從cxq或EntryList中移除
    UnlinkAfterAcquire (Self, &node) ;
    if (_succ == Self) _succ = NULL ;
    if (_Responsible == Self) {
        _Responsible = NULL ;
        OrderAccess::fence();
    }
    ...
    return ;
}

主要步驟有3步:

  1. 將當(dāng)前線程插入到cxq隊(duì)列的隊(duì)首
  2. 然后park當(dāng)前線程
  3. 當(dāng)被喚醒后再嘗試獲得鎖

這里需要特別說明的是_Responsible_succ兩個(gè)字段的作用:

當(dāng)競(jìng)爭(zhēng)發(fā)生時(shí),選取一個(gè)線程作為_Responsible养篓,_Responsible線程調(diào)用的是有時(shí)間限制的park方法秃流,其目的是防止出現(xiàn)擱淺現(xiàn)象。

_succ線程是在線程釋放鎖是被設(shè)置柳弄,其含義是Heir presumptive舶胀,也就是我們上面說的假定繼承人。

重量級(jí)鎖的釋放

重量級(jí)鎖釋放的代碼在ObjectMonitor::exit

void ATTR ObjectMonitor::exit(bool not_suspended, TRAPS) {
   Thread * Self = THREAD ;
   // 如果_owner不是當(dāng)前線程
   if (THREAD != _owner) {
     // 當(dāng)前線程是之前持有輕量級(jí)鎖的線程碧注。由輕量級(jí)鎖膨脹后還沒調(diào)用過enter方法嚣伐,_owner會(huì)是指向Lock Record的指針。
     if (THREAD->is_lock_owned((address) _owner)) {
       assert (_recursions == 0, "invariant") ;
       _owner = THREAD ;
       _recursions = 0 ;
       OwnerIsThread = 1 ;
     } else {
       // 異常情況:當(dāng)前不是持有鎖的線程
       TEVENT (Exit - Throw IMSX) ;
       assert(false, "Non-balanced monitor enter/exit!");
       if (false) {
          THROW(vmSymbols::java_lang_IllegalMonitorStateException());
       }
       return;
     }
   }
   // 重入計(jì)數(shù)器還不為0萍丐,則計(jì)數(shù)器-1后返回
   if (_recursions != 0) {
     _recursions--;        // this is simple recursive enter
     TEVENT (Inflated exit - recursive) ;
     return ;
   }

   // _Responsible設(shè)置為null
   if ((SyncFlags & 4) == 0) {
      _Responsible = NULL ;
   }

   ...

   for (;;) {
      assert (THREAD == _owner, "invariant") ;

      // Knob_ExitPolicy默認(rèn)為0
      if (Knob_ExitPolicy == 0) {
         // code 1:先釋放鎖轩端,這時(shí)如果有其他線程進(jìn)入同步塊則能獲得鎖
         OrderAccess::release_store_ptr (&_owner, NULL) ;   // drop the lock
         OrderAccess::storeload() ;                         // See if we need to wake a successor
         // code 2:如果沒有等待的線程或已經(jīng)有假定繼承人
         if ((intptr_t(_EntryList)|intptr_t(_cxq)) == 0 || _succ != NULL) {
            TEVENT (Inflated exit - simple egress) ;
            return ;
         }
         TEVENT (Inflated exit - complex egress) ;

         // code 3:要執(zhí)行之后的操作需要重新獲得鎖,即設(shè)置_owner為當(dāng)前線程
         if (Atomic::cmpxchg_ptr (THREAD, &_owner, NULL) != NULL) {
            return ;
         }
         TEVENT (Exit - Reacquired) ;
      } 
      ...

      ObjectWaiter * w = NULL ;
      // code 4:根據(jù)QMode的不同會(huì)有不同的喚醒策略逝变,默認(rèn)為0
      int QMode = Knob_QMode ;
     
      if (QMode == 2 && _cxq != NULL) {
          // QMode == 2 : cxq中的線程有更高優(yōu)先級(jí)船万,直接喚醒cxq的隊(duì)首線程
          w = _cxq ;
          assert (w != NULL, "invariant") ;
          assert (w->TState == ObjectWaiter::TS_CXQ, "Invariant") ;
          ExitEpilog (Self, w) ;
          return ;
      }

      if (QMode == 3 && _cxq != NULL) {
          // 將cxq中的元素插入到EntryList的末尾
          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 ;
          }

          // Append the RATs to the EntryList
          // TODO: organize EntryList as a CDLL so we can locate the tail in constant-time.
          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 ;
          }

          // Fall thru into code that tries to wake a successor from EntryList
      }

      if (QMode == 4 && _cxq != NULL) {
          // 將cxq插入到EntryList的隊(duì)首
          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
      }

      w = _EntryList  ;
      if (w != NULL) {
          // 如果EntryList不為空,則直接喚醒EntryList的隊(duì)首元素
          assert (w->TState == ObjectWaiter::TS_ENTER, "invariant") ;
          ExitEpilog (Self, w) ;
          return ;
      }

      // EntryList為null骨田,則處理cxq中的元素
      w = _cxq ;
      if (w == NULL) continue ;

      // 因?yàn)橹笠獙xq的元素移動(dòng)到EntryList耿导,所以這里將cxq字段設(shè)置為null
      for (;;) {
          assert (w != NULL, "Invariant") ;
          ObjectWaiter * u = (ObjectWaiter *) Atomic::cmpxchg_ptr (NULL, &_cxq, w) ;
          if (u == w) break ;
          w = u ;
      }
      TEVENT (Inflated exit - drain cxq into EntryList) ;

      assert (w != NULL              , "invariant") ;
      assert (_EntryList  == NULL    , "invariant") ;


      if (QMode == 1) {
         // QMode == 1 : 將cxq中的元素轉(zhuǎn)移到EntryList,并反轉(zhuǎn)順序
         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‘
         // 將cxq中的元素轉(zhuǎn)移到EntryList
         _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 ;
         }
      }


      // _succ不為null态贤,說明已經(jīng)有個(gè)繼承人了舱呻,所以不需要當(dāng)前線程去喚醒,減少上下文切換的比率
      if (_succ != NULL) continue;

      w = _EntryList  ;
      // 喚醒EntryList第一個(gè)元素
      if (w != NULL) {
          guarantee (w->TState == ObjectWaiter::TS_ENTER, "invariant") ;
          ExitEpilog (Self, w) ;
          return ;
      }
   }
}

在進(jìn)行必要的鎖重入判斷以及自旋優(yōu)化后,進(jìn)入到主要邏輯:

code 1 設(shè)置owner為null箱吕,即釋放鎖芥驳,這個(gè)時(shí)刻其他的線程能獲取到鎖。這里是一個(gè)非公平鎖的優(yōu)化茬高;

code 2 如果當(dāng)前沒有等待的線程則直接返回就好了兆旬,因?yàn)椴恍枰獑拘哑渌€程≡踉裕或者如果說succ不為null丽猬,代表當(dāng)前已經(jīng)有個(gè)"醒著的"繼承人線程,那當(dāng)前線程不需要喚醒任何線程熏瞄;

code 3 當(dāng)前線程重新獲得鎖脚祟,因?yàn)橹笠僮鱟xq和EntryList隊(duì)列以及喚醒線程;

code 4根據(jù)QMode的不同强饮,會(huì)執(zhí)行不同的喚醒策略由桌;

根據(jù)QMode的不同,有不同的處理方式:

  1. QMode = 2且cxq非空:取cxq隊(duì)列隊(duì)首的ObjectWaiter對(duì)象邮丰,調(diào)用ExitEpilog方法行您,該方法會(huì)喚醒ObjectWaiter對(duì)象的線程,然后立即返回剪廉,后面的代碼不會(huì)執(zhí)行了邑雅;
  2. QMode = 3且cxq非空:把cxq隊(duì)列插入到EntryList的尾部;
  3. QMode = 4且cxq非空:把cxq隊(duì)列插入到EntryList的頭部妈经;
  4. QMode = 0:暫時(shí)什么都不做,繼續(xù)往下看捧书;

只有QMode=2的時(shí)候會(huì)提前返回吹泡,等于0、3经瓷、4的時(shí)候都會(huì)繼續(xù)往下執(zhí)行:

1.如果EntryList的首元素非空搪缨,就取出來調(diào)用ExitEpilog方法殊者,該方法會(huì)喚醒ObjectWaiter對(duì)象的線程,然后立即返回;
2.如果EntryList的首元素為空纽乱,就將cxq的所有元素放入到EntryList中,然后再?gòu)腅ntryList中取出來隊(duì)首元素執(zhí)行ExitEpilog方法在旱,然后立即返回挪略;

以上對(duì)QMode的歸納參考了這篇文章。另外說下锋恬,關(guān)于如何編譯JVM屯换,可以看看該博主的這篇文章,該博主弄了一個(gè)docker鏡像,傻瓜編譯~

QMode默認(rèn)為0彤悔,結(jié)合上面的流程我們可以看這么個(gè)demo:

public class SyncDemo {

    public static void main(String[] args) {

        SyncDemo syncDemo1 = new SyncDemo();
        syncDemo1.startThreadA();
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        syncDemo1.startThreadB();
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        syncDemo1.startThreadC();
       

    }

    final Object lock = new Object();


    public void startThreadA() {
        new Thread(() -> {
            synchronized (lock) {
                System.out.println("A get lock");
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("A release lock");
            }
        }, "thread-A").start();
    }

    public void startThreadB() {
        new Thread(() -> {
            synchronized (lock) {
                System.out.println("B get lock");
            }
        }, "thread-B").start();
    }

    public void startThreadC() {
        new Thread(() -> {
            synchronized (lock) {

                System.out.println("C get lock");
            }
        }, "thread-C").start();
    }


}

默認(rèn)策略下嘉抓,在A釋放鎖后一定是C線程先獲得鎖。因?yàn)樵讷@取鎖時(shí)晕窑,是將當(dāng)前線程插入到cxq的頭部抑片,而釋放鎖時(shí),默認(rèn)策略是:如果EntryList為空杨赤,則將cxq中的元素按原有順序插入到到EntryList敞斋,并喚醒第一個(gè)線程。也就是當(dāng)EntryList為空時(shí)望拖,是后來的線程先獲取鎖渺尘。這點(diǎn)JDK中的Lock機(jī)制是不一樣的。

Synchronized和ReentrantLock的區(qū)別

原理弄清楚了说敏,順便總結(jié)了幾點(diǎn)Synchronized和ReentrantLock的區(qū)別:

  1. Synchronized是JVM層次的鎖實(shí)現(xiàn)鸥跟,ReentrantLock是JDK層次的鎖實(shí)現(xiàn);
  2. Synchronized的鎖狀態(tài)是無法在代碼中直接判斷的盔沫,但是ReentrantLock可以通過ReentrantLock#isLocked判斷医咨;
  3. Synchronized是非公平鎖,ReentrantLock是可以是公平也可以是非公平的架诞;
  4. Synchronized是不可以被中斷的拟淮,而ReentrantLock#lockInterruptibly方法是可以被中斷的;
  5. 在發(fā)生異常時(shí)Synchronized會(huì)自動(dòng)釋放鎖(由javac編譯時(shí)自動(dòng)實(shí)現(xiàn))谴忧,而ReentrantLock需要開發(fā)者在finally塊中顯示釋放鎖很泊;
  6. ReentrantLock獲取鎖的形式有多種:如立即返回是否成功的tryLock(),以及等待指定時(shí)長(zhǎng)的獲取,更加靈活沾谓;
  7. Synchronized在特定的情況下對(duì)于已經(jīng)在等待的線程是后來的線程先獲得鎖(上文有說)委造,而ReentrantLock對(duì)于已經(jīng)在等待的線程一定是先來的線程先獲得鎖;

End

總的來說Synchronized的重量級(jí)鎖和ReentrantLock的實(shí)現(xiàn)上還是有很多相似的均驶,包括其數(shù)據(jù)結(jié)構(gòu)昏兆、掛起線程方式等等。在日常使用中妇穴,如無特殊要求用Synchronized就夠了爬虱。你深入了解這兩者其中一個(gè)的實(shí)現(xiàn),了解另外一個(gè)或其他鎖機(jī)制都比較容易腾它,這也是我們常說的技術(shù)上的相通性跑筝。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市瞒滴,隨后出現(xiàn)的幾起案子继蜡,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,042評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件稀并,死亡現(xiàn)場(chǎng)離奇詭異仅颇,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)碘举,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,996評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門忘瓦,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人引颈,你說我怎么就攤上這事耕皮。” “怎么了蝙场?”我有些...
    開封第一講書人閱讀 156,674評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵凌停,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我售滤,道長(zhǎng)罚拟,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,340評(píng)論 1 283
  • 正文 為了忘掉前任完箩,我火速辦了婚禮赐俗,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘弊知。我一直安慰自己阻逮,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,404評(píng)論 5 384
  • 文/花漫 我一把揭開白布秩彤。 她就那樣靜靜地躺著叔扼,像睡著了一般。 火紅的嫁衣襯著肌膚如雪漫雷。 梳的紋絲不亂的頭發(fā)上瓜富,一...
    開封第一講書人閱讀 49,749評(píng)論 1 289
  • 那天,我揣著相機(jī)與錄音珊拼,去河邊找鬼。 笑死流炕,一個(gè)胖子當(dāng)著我的面吹牛澎现,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播每辟,決...
    沈念sama閱讀 38,902評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼剑辫,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了渠欺?” 一聲冷哼從身側(cè)響起妹蔽,我...
    開封第一講書人閱讀 37,662評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后胳岂,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體编整,經(jīng)...
    沈念sama閱讀 44,110評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,451評(píng)論 2 325
  • 正文 我和宋清朗相戀三年乳丰,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了掌测。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,577評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡产园,死狀恐怖汞斧,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情什燕,我是刑警寧澤粘勒,帶...
    沈念sama閱讀 34,258評(píng)論 4 328
  • 正文 年R本政府宣布,位于F島的核電站屎即,受9級(jí)特大地震影響庙睡,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜剑勾,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,848評(píng)論 3 312
  • 文/蒙蒙 一埃撵、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧虽另,春花似錦暂刘、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,726評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至族展,卻和暖如春森缠,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背仪缸。 一陣腳步聲響...
    開封第一講書人閱讀 31,952評(píng)論 1 264
  • 我被黑心中介騙來泰國(guó)打工贵涵, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人恰画。 一個(gè)月前我還...
    沈念sama閱讀 46,271評(píng)論 2 360
  • 正文 我出身青樓宾茂,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親拴还。 傳聞我的和親對(duì)象是個(gè)殘疾皇子跨晴,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,452評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容