美文网首页
Java中的synchronized关键字(三)

Java中的synchronized关键字(三)

作者: buzzerrookie | 来源:发表于2019-03-03 10:24 被阅读0次

上一篇文章分析了如何获得监视器,本文分析如何释放监视器。

释放监视器

释放监视器主要与ExitEpilog函数和exit函数有关。

ExitEpilog函数

ExitEpilog函数在释放监视器的过程中多次用到,其代码如下所示。

void ObjectMonitor::ExitEpilog (Thread * Self, ObjectWaiter * Wakee) {
   assert (_owner == Self, "invariant") ;

   // Exit protocol:
   // 1. ST _succ = wakee
   // 2. membar #loadstore|#storestore;
   // 2. ST _owner = NULL
   // 3. unpark(wakee)

   _succ = Knob_SuccEnabled ? Wakee->_thread : NULL ;
   ParkEvent * Trigger = Wakee->_event ;

   // Hygiene -- once we've set _owner = NULL we can't safely dereference Wakee again.
   // The thread associated with Wakee may have grabbed the lock and "Wakee" may be
   // out-of-scope (non-extant).
   Wakee  = NULL ;

   // Drop the lock
   OrderAccess::release_store_ptr (&_owner, NULL) ;
   OrderAccess::fence() ;                               // ST _owner vs LD in unpark()

   if (SafepointSynchronize::do_call_back()) {
      TEVENT (unpark before SAFEPOINT) ;
   }

   DTRACE_MONITOR_PROBE(contended__exit, this, object(), Self);
   Trigger->unpark() ;

   // Maintain stats and report events to JVMTI
   if (ObjectMonitor::_sync_Parks != NULL) {
      ObjectMonitor::_sync_Parks->inc() ;
   }
}

ExitEpilog函数的作用如下:

  • 首先将监视器的_owner字段置为NULL;
  • 然后使用ParkEvent类的unpark函数唤醒参数Wakee包装的线程,需要注意的是ObjectWaiter类的构造函数为ObjectWaiter类实例保存了线程用于同步的ParkEvent。
ObjectWaiter::ObjectWaiter(Thread* thread) {
  _next     = NULL;
  _prev     = NULL;
  _notified = 0;
  TState    = TS_RUN ;
  _thread   = thread;
  _event    = thread->_ParkEvent ;
  _active   = false;
  assert (_event != NULL, "invariant") ;
}

exit函数

exit函数代码如下:

void ATTR ObjectMonitor::exit(bool not_suspended, TRAPS) {
   Thread * Self = THREAD ;
   if (THREAD != _owner) {
     if (THREAD->is_lock_owned((address) _owner)) { // _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 ; // 将_owner指向线程
       _recursions = 0 ; // 此时肯定不是重入
       OwnerIsThread = 1 ; // 当前_owner是线程指针
     } 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;
     }
   }

   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 ;
   }

#if INCLUDE_TRACE
   // get the owner's thread id for the MonitorEnter event
   // if it is enabled and the thread isn't suspended
   if (not_suspended && Tracing::is_event_enabled(TraceJavaMonitorEnterEvent)) {
     _previous_owner_tid = SharedRuntime::get_java_tid(Self);
   }
#endif

   for (;;) {
      assert (THREAD == _owner, "invariant") ; // 此时参数线程还持有监视器

      // 省略一些代码,没太看懂这部分

      guarantee (_owner == THREAD, "invariant") ;

      ObjectWaiter * w = NULL ;
      int QMode = Knob_QMode ;
      // cxq链表和EntryList链表均不为空的情况,接下来的三个if很显然要求cxq链表不为空,EntryList链表不为空的要求在1188行
      if (QMode == 2 && _cxq != NULL) { // Knob_QMode是2意味着cxq链表的优先级比EntryList链表高,优先从cxq中唤醒第一个线程
          // QMode == 2 : cxq has precedence over EntryList.
          // Try to directly wake a successor from the cxq.
          // If successful, the successor will need to unlink itself from cxq.
          w = _cxq ;
          assert (w != NULL, "invariant") ;
          assert (w->TState == ObjectWaiter::TS_CXQ, "Invariant") ;
          ExitEpilog (Self, w) ;
          return ;
      }

      if (QMode == 3 && _cxq != NULL) { // Knob_QMode是3意味着需要将cxq链表中的元素链接到EntryList链表后面
          // Aggressively drain cxq into EntryList at the first opportunity.
          // This policy ensure that recently-run threads live at the head of EntryList.
          // Drain _cxq into EntryList - bulk transfer.
          // First, detach _cxq.
          // The following loop is tantamount to: w = swap (&cxq, NULL)
          w = _cxq ;
          for (;;) { // 使_cxq为NULL,w指向原cxq链表
             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) { // cxq链表是单向的,为了接到EntryList需要变为双向的
              guarantee (p->TState == ObjectWaiter::TS_CXQ, "Invariant") ;
              p->TState = ObjectWaiter::TS_ENTER ; // 因为会进入EntryList链表,所以将等待状态从TS_CXQ变为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 ;
          }
          // 此时原cxq链表已经链接到EntryList后面了
          // Fall thru into code that tries to wake a successor from EntryList
      }

      if (QMode == 4 && _cxq != NULL) { // Knob_QMode是4意味着需要将cxq链表中的元素链接到EntryList链表前面
          // Aggressively drain cxq into EntryList at the first opportunity.
          // This policy ensure that recently-run threads live at the head of EntryList.

          // Drain _cxq into EntryList - bulk transfer.
          // First, detach _cxq.
          // The following loop is tantamount to: w = swap (&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 ;
          // 此时原cxq链表已经链接到EntryList链表前面了,_EntryList指向新的EntryList链表
          // Fall thru into code that tries to wake a successor from EntryList
      }
      // 对上面两个if判断来说,w指向新的EntryList链表,若不符合上述判断条件则w依然指向原EntryList链表
      w = _EntryList  ;
      if (w != NULL) { // 若EntryList链表不空,则唤醒EntryList链表中的第一个线程
          // I'd like to write: guarantee (w->_thread != Self).
          // But in practice an exiting thread may find itself on the EntryList.
          // Lets say thread T1 calls O.wait().  Wait() enqueues T1 on O's waitset and
          // then calls exit().  Exit release the lock by setting O._owner to NULL.
          // Lets say T1 then stalls.  T2 acquires O and calls O.notify().  The
          // notify() operation moves T1 from O's waitset to O's EntryList. T2 then
          // release the lock "O".  T2 resumes immediately after the ST of null into
          // _owner, above.  T2 notices that the EntryList is populated, so it
          // reacquires the lock and then finds itself on the EntryList.
          // Given all that, we have to tolerate the circumstance where "w" is
          // associated with Self.
          assert (w->TState == ObjectWaiter::TS_ENTER, "invariant") ;
          ExitEpilog (Self, w) ;
          return ;
      }
      // cxq链表和EntryList链表均为空时重试
      // If we find that both _cxq and EntryList are null then just
      // re-run the exit protocol from the top.
      w = _cxq ;
      if (w == NULL) continue ;
      // 以下是cxq链表不为空但EntryList链表为空的情况
      // Drain _cxq into EntryList - bulk transfer.
      // First, detach _cxq.
      // The following loop is tantamount to: w = swap (&cxq, 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") ;

      // Convert the LIFO SLL anchored by _cxq into a DLL.
      // The list reorganization step operates in O(LENGTH(w)) time.
      // It's critical that this step operate quickly as
      // "Self" still holds the outer-lock, restricting parallelism
      // and effectively lengthening the critical section.
      // Invariant: s chases t chases u.
      // TODO-FIXME: consider changing EntryList from a DLL to a CDLL so
      // we have faster access to the tail.

      if (QMode == 1) { // Knob_QMode是1意味着EntryList链表为空,需要使cxq链表成为EntryList链表并反转顺序
         // QMode == 1 : drain cxq to EntryList, reversing order
         // We also reverse the order of the list.
         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 { // 其他情况只需要使cxq链表成为EntryList链表,不需要反转
         // 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 ;
         }
      }

      // In 1-0 mode we need: ST EntryList; MEMBAR #storestore; ST _owner = NULL
      // The MEMBAR is satisfied by the release_store() operation in ExitEpilog().

      // See if we can abdicate to a spinner instead of waking a thread.
      // A primary goal of the implementation is to reduce the
      // context-switch rate.
      if (_succ != NULL) continue;

      w = _EntryList  ;
      if (w != NULL) { // 唤醒新EntryList链表中的第一个线程
          guarantee (w->TState == ObjectWaiter::TS_ENTER, "invariant") ;
          ExitEpilog (Self, w) ;
          return ;
      }
   }
}

exit函数的简要功能如下:

  • 首先检查参数线程是否持有监视器,如果是递归重入的情况直接返回,否则继续;
  • 然后根据cxq链表和EntryList链表的不同情况和Knob_QMode值唤醒不同链表上的线程。

cxq链表和EntryList链表的情况有以下三种:

  1. cxq链表和EntryList链表均不为空的情况:
    • Knob_QMode是2意味着cxq链表的优先级比EntryList链表高,优先从cxq中唤醒第一个线程;
    • Knob_QMode是3意味着需要将cxq链表中的元素链接到EntryList链表后面,然后唤醒新EntryList链表中的第一个线程;
    • Knob_QMode是4意味着需要将cxq链表中的元素链接到EntryList链表前面,然后唤醒新EntryList链表中的第一个线程;
    • 其他情况唤醒EntryList链表中的第一个线程;
  2. cxq链表和EntryList链表均为空时需要重试;
  3. cxq链表不为空但EntryList链表为空的情况:
    • Knob_QMode是1意味着EntryList链表为空,需要使cxq链表成为EntryList链表并反转顺序,然后唤醒新EntryList链表中的第一个线程;
    • 其他情况只需要使cxq链表成为EntryList链表,不需要反转,然后唤醒新EntryList链表中的第一个线程。

Knob_QMode是定义在文件中的objectMonitor.cpp全局变量,默认值是0。我没有找到如何在启动虚拟机时配置该值,但可以在编译时修改代码指定。

相关文章

网友评论

      本文标题:Java中的synchronized关键字(三)

      本文链接:https://www.haomeiwen.com/subject/omjjeqtx.html