Java 對象鎖 Monitor 的實現

1. Java 中的 Monitor

Java synchronized 關鍵字 中介紹過synchronize的實現原理,其實無論是同步方法ACC_SYNCHRONIZED還是同步代碼塊 monitorentermonitorexit都是基於Monitor實現的。Monitor 是 Java 中用以實現線程之間的互斥與協作的主要手段,它可以看成是對象的鎖,每一個對象都有且僅有一個 Monitor。它位於 Java 對象的對象頭裏面,職責是保證同一時間只有一個線程能夠訪問被保護的數據和代碼,其發生作用時的特點是:

  1. 對象的目標代碼都被互斥地執行。一個 Monitor 只允許一個線程持有,任一個線程訪問被保護的代碼都需要獲得這個“許可”,執行完畢時釋放所有權
  2. 提供信號機制:允許正持有 Monitor 的線程暫時放棄持有,等待某種條件成立後,進程可以通知正在等待這個條件變量的線程,使其可以重新去嘗試持有 Monitor

2. Monitor 實現鎖分配

2.1 Monitor 的結構

在 Java 虛擬機 (HotSpot)中,Monitor 基於C++實現,其數據結構爲 objectMonitor.hpp ,比較關鍵的屬性如下:

 ObjectMonitor() {
    ......
    // 用來記錄該線程獲取鎖的次數
    _count        = 0;
     // 鎖的重入次數
    _recursions   = 0;
    // 指向持有ObjectMonitor對象的線程
    _owner        = NULL;
    // 存放處於wait狀態的線程隊列
    _WaitSet      = NULL;
    // 存放處於等待鎖block狀態的線程隊列
    _EntryList    = NULL ;
    ......
  }

鎖相關主要的流程如下:

  1. 當多個線程同時訪問一段同步代碼時,首先進入_EntryList隊列中,當某個線程獲取到對象的Monitor後進入_Owner區域並把Monitor中的_owner變量設置爲當前線程,同時Monitor中的計數器_count自增1,表示獲得對象鎖
  2. 若持有Monitor的線程調用wait()方法,將釋放當前持有的 Monitor_owner變量恢復爲 null,_count自減1,同時該線程進入_WaitSet集合中等待被喚醒。若當前線程執行完畢也會釋放Monitor(鎖)並復位變量的值,以便其他線程能夠獲取Monitor(鎖)
  3. Entry Set中等待的線程狀態是 Waiting for monitor entry,而在 Wait Set中等待的線程狀態是 in Object.wait()

在這裏插入圖片描述

2.2 獲取鎖流程

objectMonitor.cpp 爲實現鎖相關操作的關鍵類,其中 enter() 函數爲其重量級鎖獲取的入口

在這裏插入圖片描述

void ATTR ObjectMonitor::enter(TRAPS) {
  Thread * const Self = THREAD ;
  void * cur ;
  // CAS嘗試把monitor的`_owner`字段設置爲當前線程
  cur = Atomic::cmpxchg_ptr (Self, &_owner, NULL) ;
  // 獲取鎖失敗
  if (cur == NULL) {
     // Either ASSERT _recursions == 0 or explicitly set _recursions = 0.
     assert (_recursions == 0   , "invariant") ;
     assert (_owner      == Self, "invariant") ;
     // CONSIDER: set or assert OwnerIsThread == 1
     return ;
  }
  // 舊值和當前線程一樣,則當前線程已經持有鎖,此次爲重入,_recursions自增,並獲得鎖
  if (cur == Self) {
     // TODO-FIXME: check for integer overflow!  BUGID 6557169.
     _recursions ++ ;
     return ;
  }

  // 當前線程是第一次進入該 monitor,設置_recursions爲1,_owner爲當前線程
  if (Self->is_lock_owned ((address)cur)) {
    assert (_recursions == 0, "internal state error");
    _recursions = 1 ;
    // Commute owner from a thread-specific on-stack BasicLockObject address to
    // a full-fledged "Thread *".
    _owner = Self ;
    OwnerIsThread = 1 ;
    return ;
  }

  // We've encountered genuine contention.
  assert (Self->_Stalled == 0, "invariant") ;
  Self->_Stalled = intptr_t(this) ;

  // Try one round of spinning *before* enqueueing Self
  // and before going through the awkward and expensive state
  // transitions.  The following spin is strictly optional ...
  // Note that if we acquire the monitor from an initial spin
  // we forgo posting JVMTI events and firing DTRACE probes.
  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 ;
  }

  assert (_owner != Self          , "invariant") ;
  assert (_succ  != Self          , "invariant") ;
  assert (Self->is_Java_thread()  , "invariant") ;
  JavaThread * jt = (JavaThread *) Self ;
  assert (!SafepointSynchronize::is_at_safepoint(), "invariant") ;
  assert (jt->thread_state() != _thread_blocked   , "invariant") ;
  assert (this->object() != NULL  , "invariant") ;
  assert (_count >= 0, "invariant") ;

  // Prevent deflation at STW-time.  See deflate_idle_monitors() and is_busy().
  // Ensure the object-monitor relationship remains stable while there's contention.
  Atomic::inc_ptr(&_count);

  EventJavaMonitorEnter event;

  { // Change java thread status to indicate blocked on monitor enter.
    JavaThreadBlockedOnMonitorEnterState jtbmes(jt, this);

    Self->set_current_pending_monitor(this);

    DTRACE_MONITOR_PROBE(contended__enter, this, object(), jt);
    if (JvmtiExport::should_post_monitor_contended_enter()) {
      JvmtiExport::post_monitor_contended_enter(jt, this);

      // The current thread does not yet own the monitor and does not
      // yet appear on any queues that would get it made the successor.
      // This means that the JVMTI_EVENT_MONITOR_CONTENDED_ENTER event
      // handler cannot accidentally consume an unpark() meant for the
      // ParkEvent associated with this ObjectMonitor.
    }

    OSThreadContendState osts(Self->osthread());
    ThreadBlockInVM tbivm(jt);

    // 自旋執行 ObjectMonitor::EnterI 方法等待鎖的釋放
    for (;;) {
      jt->set_suspend_equivalent();
      // cleared by handle_special_suspend_equivalent_condition()
      // or java_suspend_self()

      EnterI (THREAD) ;

      if (!ExitSuspendEquivalent(jt)) break ;

      //
      // We have acquired the contended monitor, but while we were
      // waiting another thread suspended us. We don't want to enter
      // the monitor while suspended because that would surprise the
      // thread that suspended us.
      //
          _recursions = 0 ;
      _succ = NULL ;
      exit (false, Self) ;

      jt->java_suspend_self();
    }
    Self->set_current_pending_monitor(NULL);

    // We cleared the pending monitor info since we've just gotten past
    // the enter-check-for-suspend dance and we now own the monitor free
    // and clear, i.e., it is no longer pending. The ThreadBlockInVM
    // destructor can go to a safepoint at the end of this block. If we
    // do a thread dump during that safepoint, then this thread will show
    // as having "-locked" the monitor, but the OS and java.lang.Thread
    // states will still report that the thread is blocked trying to
    // acquire it.
  }
  
   ......
   
  OM_PERFDATA_OP(ContendedLockAttempts, inc());
}

2.3 釋放鎖流程

objectMonitor.cppexit()函數爲釋放鎖操作,其大致流程如下
在這裏插入圖片描述

void ATTR ObjectMonitor::exit(bool not_suspended, TRAPS) {
   Thread * Self = THREAD ;
    // 如果當前線程未持有 Monitor
   if (THREAD != _owner) {
     if (THREAD->is_lock_owned((address) _owner)) {
       // Transmute _owner from a BasicLock pointer to a Thread address.
       // We don't need to hold _mutex for this transition.
       // Non-null to Non-null is safe as long as all readers can
       // tolerate either flavor.
       assert (_recursions == 0, "invariant") ;
       _owner = THREAD ;
       _recursions = 0 ;
       OwnerIsThread = 1 ;
     } else {
       // NOTE: we need to handle unbalanced monitor enter/exit
       // in native code by throwing an exception.
       // TODO: Throw an IllegalMonitorStateException ?
       TEVENT (Exit - Throw IMSX) ;
       assert(false, "Non-balanced monitor enter/exit!");
       if (false) {
          THROW(vmSymbols::java_lang_IllegalMonitorStateException());
       }
       return;
     }
   }
   // 當前線程持有 Monitor,如果_recursions次數不爲0,自減
   if (_recursions != 0) {
     _recursions--;        // this is simple recursive enter
     TEVENT (Inflated exit - recursive) ;
     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 ;
   }
  // 根據不同的 QMode,從 cxq 或 _EntryList 中獲取頭節點,通過ObjectMonitor::ExitEpilog方法喚醒
  // 該節點封裝的線程,喚醒操作最終由unpark完成
    ...... 
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章