09.什么是synchronized的重量级锁?(重量级锁原理)

  本篇文章为你整理了09.什么是synchronized的重量级锁?(重量级锁原理)的详细内容,包含有重量级锁有哪些 重量级锁原理 重量级锁轻量级锁 重量级锁和轻量级锁区别 09.什么是synchronized的重量级锁?,希望能帮助你了解 09.什么是synchronized的重量级锁?。

  大家好,我是王有志。关注王有志,一起聊技术,聊游戏,聊在外漂泊的生活。

  今天我们继续学习synchronized的升级过程,目前只剩下最后一步了:轻量级锁- 重量级锁。

  通过今天的内容,希望能帮助大家解答synchronized都问啥?中除锁粗化,锁消除以及Java 8对synchronized的优化外全部的问题。

  获取重量级锁

  从源码揭秘偏向锁的升级 最后,看到synchronizer#slow_enter如果存在竞争,会调用ObjectSynchronizer::inflate方法,进行轻量级锁的升级(膨胀)。

  Tips:

  

void ObjectSynchronizer::slow_enter(Handle obj, BasicLock* lock, TRAPS) {

 

   ......

   ObjectSynchronizer::inflate(THREAD, obj(), inflate_cause_monitor_enter)- enter(THREAD);

  

 

  通过ObjectSynchronizer::inflate获取重量级锁ObjectMonitor,然后执行ObjectMonitor::enter方法。

  Tips:

  关于线程你必须知道的8个问题(中)中提到过该方法;

  问题是锁升级(膨胀),但重点不在ObjectSynchronizer::inflate,因此代码分析放在重量级锁源码分析中。

  了解ObjectMonitor::enter的逻辑前,先来看ObjectMonitor的结构:

  

class ObjectMonitor {

 

   private:

   // 保存与ObjectMonitor关联Object的markOop

   volatile markOop _header;

   // 与ObjectMonitor关联的Object

   void* volatile _object;

   protected:

   // ObjectMonitor的拥有者

   void * volatile _owner;

   // 递归计数

   volatile intptr_t _recursions;

   // 等待线程队列,cxq移入/Object.notify唤醒的线程

   ObjectWaiter * volatile _EntryList;

   private:

   // 竞争队列

   ObjectWaiter * volatile _cxq;

   // ObjectMonitor的维护线程

   Thread * volatile _Responsible;

   protected:

   // 线程挂起队列(调用Object.wait)

   ObjectWaiter * volatile _WaitSet;

  

 

  _header字段存储了Object的markOop,为什么要这样?因为锁升级后没有空间存储Object的markOop了,存储到_header中是为了在退出时能够恢复到加锁前的状态。

  Tips:

  实际上basicLock也存储了对象的markOop;

  EntryList中等待线程来自于cxq移入,或Object.notify唤醒但未执行。

  重入的实现

  objectMonito#enter方法可以拆成三个部分,首先是竞争成功或重入的场景:

  

// 获取当前线程Self

 

  Thread * const Self = THREAD;

  // CAS抢占锁,如果失败则返回_owner

  void * cur = Atomic::cmpxchg(Self, _owner, (void*)NULL);

  if (cur == NULL) {

   // CAS抢占锁成功直接返回

   return;

  // CAS失败场景

  // 重量级锁重入

  if (cur == Self) {

   // 递归计数+1

   _recursions++;

   return;

  // 当前线程是否曾持有轻量级锁

  // 可以看做是特殊的重入

  if (Self- is_lock_owned ((address)cur)) {

   // 递归计数器置为1

   _recursions = 1;

   _owner = Self;

   return;

  

 

  重入和升级的场景中,都会操作_recursions。_recursions记录了进入ObjectMonitor的次数,解锁时要经历相应次数的退出操作才能完成解锁。

  适应性自旋

  以上都是成功获取锁的场景,那么产生竞争导致失败的场景是怎样的呢?来看适应性自旋的部分,ObjectMonitor倒数第二次对“轻量”的追求:

  

// 尝试自旋来竞争锁

 

  Self- _Stalled = intptr_t(this);

  if (Knob_SpinEarly TrySpin (Self) 0) {

   Self- _Stalled = 0;

   return;

  

 

  objectMonitor#TrySpin方法是对适应性自旋的支持。Java 1.6后加入,移除默认次数的自旋,将自旋次数的决定权交给JVM。

  JVM根据锁上一次自旋情况决定,如果刚刚自旋成功,并且持有锁的线程正在执行,JVM会允许再次尝试自旋。如果该锁的自旋经常失败,那么JVM会直接跳过自旋过程。

  Tips:

  适应性自旋的原码分析放在了重量级锁源码分析中;

  objectMonitor#TryLock非常简单,关键技术依旧是CAS。

  互斥的实现

  到目前为止,无论是CAS还是自旋,都是偏向锁和轻量级锁中出现过的技术,为什么会让ObjectMonitor背上“重量级”的名声呢?

  最后是竞争失败的场景:

  

// 此处省略了修改当前线程状态的代码

 

  for (;;) {

   EnterI(THREAD);

  

 

  实际上,进入ObjectMonitor#EnterI后也是先尝试“轻量级”的加锁方式:

  

void ObjectMonitor::EnterI(TRAPS) {

 

   if (TryLock (Self) 0) {

   return;

   if (TrySpin (Self) 0) {

   return;

  

 

  接来下是重量级的真正实现:

  

// 将当前线程(Self)封装为ObjectWaiter的node

 

  ObjectWaiter node(Self);

  Self- _ParkEvent- reset();

  node._prev = (ObjectWaiter *) 0xBAD;

  node.TState = ObjectWaiter::TS_CXQ;

  // 将node插入到cxq的头部

  ObjectWaiter * nxt;

  for (;;) {

   node._next = nxt = _cxq;

   if (Atomic::cmpxchg( node, _cxq, nxt) == nxt)

   break;

   // 为了减少插入到cxq头部的次数,试试能否直接获取到锁

   if (TryLock (Self) 0) {

   return;

  

 

  逻辑一目了然,封装ObjectWaiter对象,并加入到cxq队列头部。接着往下执行:

  

// 将当前线程(Self)设置为当前ObjectMonitor的维护线程(_Responsible)

 

  // SyncFlags的默认值为0,可以通过-XX:SyncFlags设置

  if ((SyncFlags 16) == 0 nxt == NULL _EntryList == NULL) {

   Atomic::replace_if_null(Self, _Responsible);

  for (;;) {

   // 尝试设置_Responsible

   if ((SyncFlags 2) _Responsible == NULL) {

   Atomic::replace_if_null(Self, _Responsible);

   // park当前线程

   if (_Responsible == Self (SyncFlags 1)) {

   Self- _ParkEvent- park((jlong) recheckInterval);

   // 简单的退避算法,recheckInterval从1ms开始

   recheckInterval *= 8;

   if (recheckInterval MAX_RECHECK_INTERVAL) {

   recheckInterval = MAX_RECHECK_INTERVAL;

   } else {

   Self- _ParkEvent- park();

   // 尝试获取锁

   if (TryLock(Self) 0)

   break;

   if ((Knob_SpinAfterFutile 1) TrySpin(Self) 0)

   break;

   if (_succ == Self)

   _succ = NULL;

  

 

  逻辑也不复杂,不断的park当前线程,被唤醒后尝试获取锁。需要关注-XX:SyncFlags的设置:

  当SyncFlags == 0时,synchronized直接挂起线程;

  当SyncFlags == 1时,synchronized将线程挂起指定时间。

  前者是永久挂起,需要被其它线程唤醒,而后者挂起指定的时间后自动唤醒。

  Tips:关于线程你必须知道的8个问题(中)聊到过park和parkEvent,底层是通过pthread_cond_wait和pthread_cond_timedwait实现的。

  释放重量级锁

  释放重量级锁的源码和注释非常长,我们省略大部分内容,只看关键部分。

  重入锁退出

  我们知道,重入是不断增加_recursions的计数,那么退出重入的场景就非常简单了:

  

void ObjectMonitor::exit(bool not_suspended, TRAPS) {

 

   Thread * const Self = THREAD;

   // 第二次持有锁时,_recursions == 1

   // 重入场景只需要退出重入即可

   if (_recursions != 0) {

   _recursions--;

   return;

   .....

  

 

  不断的减少_recursions的计数。

  释放和写入

  JVM的实现中,当前线程是锁的持有者且没有重入时,首先会释放自己持有的锁,接着将改动写入到内存中,最后还肩负着唤醒下一个线程的责任。先来看释放和写入内存的逻辑:

  

// 置空锁的持有者

 

  OrderAccess::release_store( _owner, (void*)NULL);

  // storeload屏障,

  OrderAccess::storeload();

  // 没有竞争线程则直接退出

  if ((intptr_t(_EntryList)intptr_t(_cxq)) == 0 _succ != NULL) {

   TEVENT(Inflated exit - simple egress);

   return;

  

 

  storeload屏障,对于如下语句:

  

store1;

 

  storeLoad;

  load2

  

 

  保证store1指令的写入在load2指令执行前,对所有处理器可见。

  Tips:volatile中详细解释内存屏障。

  唤醒的策略

  执行释放锁和写入内存后,只需要唤醒下一个线程来“交接”锁的使用权。但是有两个“等待队列”:cxq和EntryList,该从哪个开始唤醒呢?

  Java 11前,根据QMode来选择不同的策略:

  QMode == 0,默认策略,将cxq放入EntryList;

  QMode == 1,翻转cxq,并放入EntryList;

  QMode == 2,直接从cxq中唤醒;

  QMode == 3,将cxq移入到EntryList的尾部;

  QMode == 4,将cxq移入到EntryList的头部。

  不同的策略导致了不同的唤醒顺序,现在你知道为什么说synchronized是非公平锁了吧?

  objectMonitor#ExitEpilog方法就很简单了,调用的是与park对应的unpark方法,这里就不多说了。

  Tips:Java 12的objectMonitor移除了QMode,也就是说只有一种唤醒策略了。

  我们对重量级锁做个总结。synchronized的重量级锁是ObjectMonitor,它使用到的关键技术有CAS和park。相较于mutex#Monitor来说,它们的本质相同,对park的封装,但ObjectMonitor是做了大量优化的复杂实现。

  我们看到了重量级锁是如何实现重入性的,以及唤醒策略导致的“不公平”。那么我们常说的synchronized保证了原子性,有序性和可见性,是如何实现的呢?

  大家可以先思考下这个问题,下篇文章会做一个全方位的总结,给synchronized收下尾。

  好了,今天就到这里了,Bye~~

  以上就是09.什么是synchronized的重量级锁?(重量级锁原理)的详细内容,想要了解更多 09.什么是synchronized的重量级锁?的内容,请持续关注盛行IT软件开发工作室。

郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。

留言与评论(共有 条评论)
   
验证码: