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完成
    ...... 
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章