再谈synchronized

独自空忆成欢 提交于 2021-01-05 10:23:21


1.synchronized如何实现同步的?
2.synchronized对象锁保存在哪里?
3.如何加锁和释放锁,1.6做了哪些优化?
4.如何将线程挂起等待和唤醒?

对象内置锁

        我们之前了解过synchronized同步普通方法和静态方法,以及同步代码块,其中的区别在于synchronized具体锁的是那个对象,是具体的实例还是类对象,即class 我们通过反编译:javap -verbose SynchronizedTest.class >sync.txt。可以看出使用synchronized之后的一些细微的变化,方法上的同步关键字,class文件反编译后会发现flags中有ACC_SYNCHRONIZED标识。

        1.方法级的同步是隐式的:同步方法的常量池中会有一个ACC_SYNCHRONIZED标志。当某个线程要访问某个方法的时候,会检查是否有ACC_SYNCHRONIZED,如果有设置,则需要先获得对象的内置锁,然后开始执行方法,方法执行之后再释放锁。

    2.同步代码块:依赖monitorenter和monitorexit相关标识,通过JVM内置锁对象来实现加锁和释放锁。

        对象头信息简介:首先每一个对象在堆内存中,实例对象会有对象头信息,对象实体信息,还有填充信息。其中头信息中包括锁状态,GC年纪,Hash值和监控monitor对象的引用等信息,所以锁相关的记录都会保存在该对象中,对象头信息MarkOOP对象中持有该对象的引用指针。具体可以参考下面的图,图来源网络。

        synchronzied实现同步用到了对象的内置锁(ObjectMonitor),锁相关的计数器和线程竞争情况都会依赖monitor对象。下面看一下JVM中改对象头中的监控对象的具体实现,该对象是在HotSpot底层C++语言编写的。具体可以在github上查看具体的源码:

[https://github.com/openjdk-mirror/jdk7u-hotspot/blob/master/src/share/vm/runtime/objectMonitor.hpp]中查看hotspot-JVM具体源码的实现,其中objectMonitor.hpp中是该对象的结构的一些定义信息,包括对象属性和方法定义等,objectMonitor.cpp是具体的实现。首先我们看一下hpp文件中objectMonitor监控对象的结构。

       3.objectMonitor.hpp中ObjectMonitor的结构:

ObjectMonitor() {
    _header       = NULL;//头结点
    _count        = 0;//锁计数器
    _waiters      = 0,//等待的线程数
    _recursions   = 0;//线程的重入次数
    _object       = NULL;
    _owner        = NULL;//当前持有锁的线程对象
    _WaitSet      = NULL;//等待线程组成的双向循环链表
    _WaitSetLock  = 0 ;
    _Responsible  = NULL ;
    _succ         = NULL ;
    _cxq          = NULL ;//多线程竞争锁进入时的单向链表
    FreeNext      = NULL ;
    _EntryList    = NULL ;//_owner从该双向循环链表中唤醒线程结点
    _SpinFreq     = 0 ;
    _SpinClock    = 0 ;
    OwnerIsThread = 0 ;
  }

      4.objectMonitor.hpp中相关方法:看到下面这些方法大家可能并不陌生,每个对象都会有相应Object中的方法,其中wait和notify,就是超类中的方法,其实底层实现就在这里。

  bool      try_enter (TRAPS) ;//尝试获取锁,返回Boolean
  void      enter(TRAPS);//获取锁,jvm触发monitorenter指令的时候,底层将调用该方法
  void      exit(TRAPS);//释放锁,jvm触发monitorexit指令的时候调用
  void      wait(jlong millis, bool interruptable, TRAPS);//获得对象锁,调用对象的wait()方法,将挂起线程
  void      notify(TRAPS);//调用对象的notify()方法,唤醒其中的一个等待线程
  void      notifyAll(TRAPS);//调用对象的notifyAll()方法,将唤醒所有的等待线程


内置锁原理

        JDK1.6版本之前,同步关键字采用的是重锁,所以先讲一下重锁的获取和释放,后面再谈1.6以后的锁升级优化。ObjectMonitor中维护了一个_cxq队列和_EntryList队列,_owner和_WaitSet等待唤醒线程队列。

        1._cxq队列多线程竞争锁进入时的单向链表;

        2._EntryList是_owner待唤醒的所有竞争线程对象

        3._owner对象:是当前获得锁的线程,当调用exit方法后,也就是出同步方法或者代码块之后,monitor对象中锁状态和等待队列会发生变化,并且通知其他线程又可以重新进行锁竞争。

        4.调用wait方法, 将会将该线程封装成等待节点信息,并存放到_WaitSet中等待,并且将线程park挂起,释放对象锁,此时owner对象会转到_WaitSet中,

        5.当调用notify()唤醒操作: _WaitSet等待的线程又会重新的请求锁资源。具体的可以看一下源码如何将线程打包成节点,存入_WaitSet链表中,出队的时候调用DequeueWaiter方法移出_waiterSet第一个结点,会触发unpark唤醒队列第一个节点中的线程。

      可以参考下图进行理解,图来源网络。

重锁加锁

      1.enter(TRAPS)是重量级锁的获锁过程,属于1.6之前的做法,1.6 jvm进行锁升级相关的优化,下一篇会针对jvm-hotspot锁升级源码来分析锁升级的过程。

      2.Atomic::cmpxchg_ptr:Atomic::cmpxchg_ptr hotspot源码文件在Atomic.hpp中static intptr_t cmpxchg_ptr(intptr_t exchange_value, volatile intptr_t* dest, intptr_t compare_value),该方法就是重中之重的CAS原子比较替换,它是jvm原子操作的基础,我们看一下hotspot中该方法的注释;

      3.cmpxchg_ptr源码的注解翻译:(1).执行* dest和compare_value的原子比较,并用exchange_value交换* dest; (2).如果比较成功,返回* dest的先前值,保证双向记忆;

void ATTR ObjectMonitor::enter(TRAPS) {
  // The following code is ordered to check the most common cases first
  // and to reduce RTS->RTO cache line upgrades on SPARC and IA32 processors.
  //首先检查最常见的情况 并减少SPARC和IA32处理器上的RTS-> RTO缓存线升级。
  //self 为当前线程
  Thread * const Self = THREAD ;
  void * cur ;
  //调用cmpxchg函数进行cas原子替换_owner为当前线程
  cur = Atomic::cmpxchg_ptr (Self, &_owner, NULL) ;
  //如果替换成功之后,cur之前的值为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 ;
  }
  //如果cas比较替换之后,如果之前的_owner指向该THREAD,那么该线程是重入,_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.
  //尝试一轮旋转*之前*排队Self并在经历尴尬和昂贵的状态转换之前。
  //以下旋转是严格可选的...请注意,如果我们从初始旋转中获取监视器,我们将放弃发布JVMTI事件并触发DTRACE探测器。
  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 ;
  }

  // Prevent deflation at STW-time.  See deflate_idle_monitors() and is_busy().
  // Ensure the object-monitor relationship remains stable while there's contention.
  //获得锁,则原子增加_count值,表示锁的计数器
  Atomic::inc_ptr(&_count);

  { // Change java thread status to indicate blocked on monitor enter.
    //更改java线程状态,并且阻止其他线程进入监视器。
    JavaThreadBlockedOnMonitorEnterState jtbmes(jt, this);

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

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

    Self->set_current_pending_monitor(this);

    //自旋执行ObjectMonitor::EnterI方法等待锁的释放
    for (;;) {
      jt->set_suspend_equivalent();
      // cleared by handle_special_suspend_equivalent_condition()
      // or java_suspend_self()
      //没有获得锁的线程,放入_EntryList中
      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 (Self) ;

      jt->java_suspend_self();
    }
    Self->set_current_pending_monitor(NULL);
  }
  //锁如果释放,则原子递减_count值
  Atomic::dec_ptr(&_count);
  assert (_count >= 0"invariant") ;
  Self->_Stalled = 0 ;

  DTRACE_MONITOR_PROBE(contended__entered, this, object(), jt);
  if (JvmtiExport::should_post_monitor_contended_entered()) {
    JvmtiExport::post_monitor_contended_entered(jt, this);
  }
  if (ObjectMonitor::_sync_ContendedLockAttempts != NULL) {
     ObjectMonitor::_sync_ContendedLockAttempts->inc() ;
  }
}

重锁获取的流程:

      1.cmpxchg_ptr替换当前线程,判断是内置锁中的_owner是否替换成成功,cmpxchg_ptr函数返回为空则代表替换成功。

      2.如果失败,则需要调用EnterI函数将当前线程封装到等待队列,并将线程挂起等待被唤醒。

重锁释放

      重锁的释放,需要做哪些事情?也就是当方法执行同步代码块之后,退出锁的逻辑,下面是具体的释放锁的过程。就是唤醒正在等待竞争的线程,也就是需要唤醒内置锁ObjectMonitor中的_EntryList 中的线程,重新获取锁,并且执行同步代码。

void ATTR ObjectMonitor::exit(TRAPS) {
   Thread * Self = THREAD ;
   if (THREAD != _owner) {
     if (THREAD->is_lock_owned((address) _owner)) {
       assert (_recursions == 0"invariant") ;
       _owner = THREAD ;
       _recursions = 0 ;
       OwnerIsThread = 1 ;
     } else {
       TEVENT (Exit - Throw IMSX) ;
       assert(false"Non-balanced monitor enter/exit!");
       if (false) {
          THROW(vmSymbols::java_lang_IllegalMonitorStateException());
       }
       return;
     }
   }

   if (_recursions != 0) {
     _recursions--;        // _recursions重入的次数递减
     TEVENT (Inflated exit - recursive) ;
     return ;
   }

   if ((SyncFlags & 4) == 0) {
      _Responsible = NULL ;
   }

   for (;;) {
      assert (THREAD == _owner, "invariant") ;
      if (Knob_ExitPolicy == 0) {
         OrderAccess::release_store_ptr (&_owner, NULL) ;   // drop the lock
         OrderAccess::storeload() ;                         // See if we need to wake a successor
         if ((intptr_t(_EntryList)|intptr_t(_cxq)) == 0 || _succ != NULL) {
            TEVENT (Inflated exit - simple egress) ;
            return ;
         }
         TEVENT (Inflated exit - complex egress) ;

         if (Atomic::cmpxchg_ptr (THREAD, &_owner, NULL) != NULL) {
            return ;
         }
         TEVENT (Exit - Reacquired) ;
      } else {
         if ((intptr_t(_EntryList)|intptr_t(_cxq)) == 0 || _succ != NULL) {
            OrderAccess::release_store_ptr (&_owner, NULL) ;   // drop the lock
            OrderAccess::storeload() ;
            // Ratify the previously observed values.
            if (_cxq == NULL || _succ != NULL) {
                TEVENT (Inflated exit - simple egress) ;
                return ;
            }

            if (Atomic::cmpxchg_ptr (THREAD, &_owner, NULL) != NULL) {
               TEVENT (Inflated exit - reacquired succeeded) ;
               return ;
            }
            TEVENT (Inflated exit - reacquired failed) ;
         } else {
            TEVENT (Inflated exit - complex egress) ;
         }
      }

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

      ObjectWaiter * w = NULL ;
      int QMode = Knob_QMode ;

      //QMode == 2:从_cxq队列中取出线程,并且调用unpark唤醒
      if (QMode == 2 && _cxq != NULL) {
          w = _cxq ;
          assert (w != NULL"invariant") ;
          assert (w->TState == ObjectWaiter::TS_CXQ, "Invariant") ;
          ExitEpilog (Self, w) ;//这里会唤醒,unpark _EntryList中等待的线程
          return ;
      }
      //QMode == 3:将_cxq队列尾插入_EntryList中
      if (QMode == 3 && _cxq != NULL) {
          w = _cxq ;
          //省去一些代码
          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 ;
          }
      }
      //QMode == 4:将_cxq队列头插入_EntryList中
      if (QMode == 4 && _cxq != NULL) {
          w = _cxq ;

          if (_EntryList != NULL) {
              q->_next = _EntryList ;
              _EntryList->_prev = q ;
          }
          _EntryList = w ;

      }

      w = _EntryList  ;
      if (w != NULL) {
          assert (w->TState == ObjectWaiter::TS_ENTER, "invariant") ;
          ExitEpilog (Self, w) ;//这里会唤醒,unpark _EntryList中等待的线程
          return ;
      }

      w = _cxq ;
      if (w == NULLcontinue ;

      // 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) ;

      //QMode == 1? _EntryList为倒置后的cxq队列
      if (QMode == 1) {
         // 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 {
         // 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 ;
         }
      }

      if (_succ != NULLcontinue;

      w = _EntryList  ;
      if (w != NULL) {
          guarantee (w->TState == ObjectWaiter::TS_ENTER, "invariant") ;
          ExitEpilog (Self, w) ;//唤醒unpark _EntryList中等待的线程
          return ;
      }
   }
}

释放的代码逻辑很长,我们主要关注一下几点:

1.QMode是不同的模式下,需要走不同的逻辑策略;

2.重点是_owner线程退出,并且唤醒_EntryList中的线程进行重新竞争锁。

总结

      以上就是hotspot-jvm中同步关键字重锁加锁流程,以及源码介绍,部分源码读起来很艰涩,有很多地方没有完全读懂,有兴趣的可以上github自行研究。研究的主要目的是加深jvm锁的概念和原理。下图是多线程锁竞争的大致流程,对象内置锁monitor对象中_cxq和EntryList以及WaitSet三个线程队列的变化过程。关于1.6以后锁升级和wait()/notify()原理,放入下一篇。


参考

  1. jdk7u-hotspot源码:

    https://github.com/openjdk-mirror/jdk7u-hotspot;

  2. https://www.cnblogs.com/kundeg/p/8422557.html ,其中部分插图是来源该博客。

微信公众号:MyClass社区

如有问题或建议,请公众号留言。

喜欢请关注

本文分享自微信公众号 - MyClass社区(MyClass_ZZ)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!