Jvm源码剖析之synchronized锁机制执行原理

简介: Jvm源码剖析之synchronized锁机制执行原理

需要用到的两个地址

Clion地址下载:https://www.jetbrains.com/clion/

jvm源码下载地址:http://hg.openjdk.java.net/jdk8/jdk8/hotspot/

Monitor监视器锁

1.png

如上图所示:我们java对象在创建之后会有对象头和对象实例数据,多线程情况下,一个线程进入到同步代码块中,会受到monitor锁的监控,将ObjectMonitor对象中的owner属性设置为自己,其他没有抢到锁的线程先进入到cxq队列中,等进入同步代码块中的线程释放锁之后,如果还未抢到锁,进入EntryList中等待下轮锁竞争。


_owner: 初始化为NUll,当有线程占有该monitor时,owner标记为该线程的唯一表示。当线程释放monitor时,owner又恢复到NULL。owner是一个临界资源,JVM是通过CAS操作来保证其线程安全的。


每个对象有一个监视器锁(monitor)。当monitor被占用时就会处于锁定状态,线程执行monitorenter指令时尝试获取monitor的所有权,过程如下:


如果monitor的进入数为0,则该线程进入monitor,然后将进入数设置为1,该线程即为monitor的所有者。
如果线程已经占有该monitor,只是重新进入,则进入monitor的进入数加1。
如果其他线程已经占用了monitor,则该线程进入阻塞状态,直到monitor的进入数为0,再重新尝试获取monitor的所有权。

Minitor锁竞争

执行 monitorenter

1、会调用 InterpreterRuntime.cpp【 src/share/vm/interpreterRuntime.cpp)的InterpreterRuntime::monitorenter 函数。561行 】

IRT_ENTRY_NO_ASYNC(void, InterpreterRuntime::monitorenter(JavaThread* thread, BasicObjectLock* elem))
#ifdef ASSERT
 thread->last_frame().interpreter_frame_verify_monitor(elem);
#endif
 if (PrintBiasedLockingStatistics) {
   Atomic::inc(BiasedLocking::slow_path_entry_count_addr());
 }
 Handle h_obj(thread, elem->obj());
 assert(Universe::heap()->is_in_reserved_or_null(h_obj()),
        "must be NULL or an object");
   //是否设置偏向锁,如果我们设置之后会为true
 if (UseBiasedLocking) {
   // Retry fast entry if bias is revoked to avoid unnecessary inflation
   ObjectSynchronizer::fast_enter(h_obj, elem->lock(), true, CHECK);
 } else {
     //不设置偏向锁会进入重量级锁的逻辑
   ObjectSynchronizer::slow_enter(h_obj, elem->lock(), CHECK);
 }
 assert(Universe::heap()->is_in_reserved_or_null(elem->obj()),
        "must be NULL or an object");

2、对于重量级锁,monitorenter的函数中(slow_enter最终)会调用ObjectSynchronizer::slow_enter

3、最终调用 ObjectMonitor::enter(对象监视器锁monitor的enter方法,说明还是要回到ObjectMonitor.cpp)

(位于:src/share/vm/runtime/objectMonitor.cpp),源码如下:

//省略一些不重要的代码
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.
  Thread * const Self = THREAD;
  void * cur ;
  /**
  通过 CAS 操作尝试把 monitor 的 _owner 字段设置为当前线程
  在ObjectMonitor::enter进入的时候会调用Actomic当中的cmpxchg_ptr;
  Atomic::cmpxchg_ptr(Self, &_owner, NULL)
  该函数属于linux内核系统中的函数,依赖cpu去做原子操作
  CAS是一个原子的赋值操作;
  作用就是将monitor对象当中的_owner设置成这个当前线程Self;
  */
  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++,如果当前线程的_owner时当前线程,锁计数+1,表明进入了新的拥有相同锁的同步代码块
  if(cur == Self){
    // TODO-FIXME: : check for integer overflow! BUGID 6557169
    _recursions ++;
    return ;
  }
/**
如果当前线程第一次来抢monitor该锁;
如果当前线程是第一次进入该monitor,如果抢到锁了;
设置_recursions为1,并且将_owner设置为当前线程;
最后返回即表示当前线程竞争到该锁;
*/
if(Self -> is_lock_owned((address)cur)){
  assert (_recursions == 0, "internal state error");
  _recursions = 1;
  // Commute error from a thread-specific on-stack BasicLockObject address to
  // a full-fledged "Thread *".
  _owner = Self;
  OwnerIsThread = 1;
  return ;
}
  //以上操作都没有抢到锁,进入循环等待机会
  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 suspend 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);
}

锁竞争总结

1、锁竞争的就是通过cas操作将ObjectMonitor对象中的owner属性值设置为当前线程,表示竞争到锁
2、如果设置之前的_owner指向当前线程,说明当前线程再次进入monitor,即重入锁,执行recursions++,记录重入的次数;(如果在这之前的上一次竞争当前线程获取得到了该锁,那么现在当次竞争当前线程又竞争到了该锁;两把锁一样;那么说明是锁重入;)
3、 如果当前线程是第一次进入该monitor,设置recursions为1,_owner为当前线程,该线程成功获得锁并返回。
4、 如果获取锁失败,则等待锁的释放


Monitor等待

(/src/share/vm/runtime/ObjectMonitor.cpp ObjectMonitor::EnterI (TRAPS)方法)

//省略部分代码
void ATTR ObjectMonitor::EnterI(THREAD){
  Thread * Self = THREAD;
  // Try the lock - TATAS  尝试获取锁
  if(TryLock (Self) > 0){
    assert (_succ        != Self              , "invariant");
    assert (_owner       == Self              , "invariant");
    assert (_Responsible != Self              , "invariant");
    return ;
  }
  //以上操作没抢到锁,进行自旋操作,看能否抢到锁
  if(TrySpin (Self) > 0){
    assert (_succ        == Self              , "invariant");
    assert (_owner       != Self              , "invariant");
    assert (_Responsible != Self              , "invariant");
    return ;
  }
  // 省略部分代码
  // 当前线程被封装成ObjectWaiter对象node(等待的线程),状态设置成ObjectWaiter::TS_CXQ
  ObjectWaiter node(Self);
  Self->ParkEvent->reset();
  node._prev  = (ObjectWaiter *) 0xBAD;
  node.TState = ObjectWaiter::TS_CXQ;
  // 通过CAS把node节点push到_cxq列表中
  ObjectWaiter * nxt;
//期间可能会有失败,循环将所有没抢到锁的线程都放入cxq中
  for(;;){
    node._next = next = _cxq;
    if(Atomic::cmpxchg_ptr(&node, &_cxq, nxt) == nxt) break;
    // Interference - the CAS failed because _cxq changed. Just retry.
    // As an optional optimization we retry the lock.
    //在放入cxq的期间尝试再次获取锁
    if(TryLock(Self) > 0){
        if(TryLock (Self) > 0){
          assert (_succ        != Self              , "invariant");
          assert (_owner       == Self              , "invariant");
          assert (_Responsible != Self              , "invariant");
          return ;
        }
     }
      // 省略部分代码
      for(;;){
        // 线程在挂起前做一下挣扎,看能不能获取到锁
        if(TryLock (Self) > 0)break;
        assert (_owner != Self, "inveriant");
        if((SyncFlags & 2) && _Reponsible == NULL){
          Atomic::cmpxchg_ptr (Self, &_Reponsible, NULL);
        }
        // park self
        if(_Responsible == Self || (SynchFlags & 1)){
          TEVENT (Inflated enter - park TIMED);
          Self->_ParkEvent->park((jlong)RecheckInterval);
          // Increase the RecheckInterval, but clamp the value.
          RecheckInterval *= 8;
          if(RecheckInterval > 1000) RecheckInterval = 1000;
        }else{
          TEVENT (Inflated enter - park UNTIMED);
          // 通过park将当前线程挂起,等待被唤醒
          Self->_ParkEvent->park();
        }
    //当挂起的线程被唤醒之后,再次尝试获取锁
        if(TryLock(Self) > 0) break;
      }
  }

锁等待总结

1、当前线程进入monitor等待方法之后,先尝试获取锁,如果没获取到进行自选操作去尝试获取锁
2、如果还没获取到锁,当前线程被封装成ObjectWaiter对象node(等待的线程),状态设置成ObjectWaiter::TS_CXQ
3、通过CAS把node节点push到_cxq列表中,期间可能会有失败,循环将所有没抢到锁的线程都放入cxq中,
在这期间继续尝试获取锁
4、再获取不到锁,会在线程挂起前获取锁,再获取不到就执行park方法挂起当前线程,等待线程被唤醒
5、当线程被唤醒之后,通过ObjectMonitor::TryLock尝试获取锁
————————————————
版权声明:本文为CSDN博主「力不竭!!!战不止!!!」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/asd1358355022/article/details/119085305



该线程被唤醒时,会从挂起的点继续执行,通过ObjectMonitor::TryLock尝试获取锁,TryLock方法实现如下:


截取部分代码

int ObjectMonitor::TryLock(Thread * Self){
  for(;;){
    void * own = owner;
    if(own != null) return 0;
    if(Atomic::cmpxchg_ptr (Self, &owner, NULL) == NULL){
      // Either guarantee recursions == 0 or set _recursions = 0.
      assert (recursions == 0,    "invariant");
      assert (_owner      == Self, "invariant");
      // CONSIDER: set or assert that OwnerIsThread == 1
      return 1;
    }
    // The lock had been freen momentarily, but we lost the race to the lock.
    // Interference -- the CAS faild.
    // we can either return -1 or retry.
    // Retry doesn't make as much sense because the lock was just acquired.
    if(true) return -1;
  }
}

Monitor锁释放

当某个持有锁的线程执行完同步代码块时,会进行锁的释放,给其他线程机会执行同步代码块。

在HotSpot中,通过推出monitor的方式实现锁的释放,并通知被阻塞的线程,具体实现位于ObjectMonitor的exit方法中。

(位于:/src/share/vm/runtime/ObjectMonitor.cpp ObjectMonitor::exit(bool not_suspended, TRAPS))

-------------------------------------------------------------------
# 截取部分代码
void ATTR ObjectMonitor::exit(bool not_suspended, TRAPS){
  Thread * Self = THREAD;
  // 省略部分代码
  if(_recursions != 0){
    _recursions--;    // this is simple recursive enter
    TEVENT (Inflated exit - recursive);
    return ;
  }
  // 省略部分代码
  //w为要被唤醒的线程
  ObjectWaiter * w = NULL;
  int QMode = Knob_QMode;
  // qmode = 2:直接绕过EntryList队列,从_cxq队列中获取线程用于竞争锁
  if(QMode == 2 && _cxq != NULL){
    //cxq不为空,拿到cxq的首结点
    w = _cxq;
  assert ( w != NULL, "invariant");
  assert ( w->TState == ObjectWaiter::TS_CXQ, "invariant");
  //唤醒线程操作
  ExitEpilog(Self, w);
  return ;
  }
  // qmode=3:cxq队列插入EntryList尾部;
  if(QMode == 3 && _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;
    }
    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;
    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 ->_next){
      guarantee(p->TState == ObjectWaiter::TS_CXQ, "Invariant");
      p->TState = ObjectWaiter::TS_ENTER;
      p->_prev = q;
      q = p;
    }
    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);
    return ;
  }
  w = _cxq;
  if(w == NULL) continue;
  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");
  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 != NULL) continue;
  w = _EntryList;
  if(w != NULL){
    guarantee(w -> TState == ObjectWater::TS_ENTER, "invariant");
    //唤醒线程
    ExitEpilog(Self, w);
    return ;
  }
}

锁释放总结

1、 退出同步代码块时 会让_recursions减1,当_recursions的值减为0时,说明(线程完全退出了同步代码块中)线程释放了锁;
2、 (释放完锁之后需要唤醒线程)根据不停的策略(策略不同唤醒不同的线程)(由QMode指定),从_cxq或_EntryList中获取头节点,通过ObjectMonitor::ExitEpilog方法唤醒该节点封装的线程,唤醒操作最终由unpack完成(将之前park的线程进行唤醒),实现如下
-----------------------------------------------------------------
# 截取部分代码
void ObjectMonitor::ExitEpilog(Thread * Self, ObjectWaiter * wakee){
  assert( _owner == Self, "invariant");
  _succ = Knob_SuccEnabled ? wakee->_thread : NULL;
  ParkEvent * Trigger = wakee->_event;
  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(unpack before SAFEPOINT);
  }
  DTRACE_MONITOR_PROBE(contended__exit, this, object(), Self);
  Trigger->unpark();  // 唤醒之前被park()挂起的线程
  // Maintain stats and report events to JVMTI
  if (ObjectMonitor::_synch_Parks != NULL){
    ObjectMonitor::_sync_Parks->inc();
  }
}

为什么monitor是重量级锁

图1:

1.png

图2:

1.png

通过图一我们可以看到应用程序(用户态)和内核(内核态)的调用时通过系统调用实现的。


通过图二我们可以看到:

内核(操作系统的内核,而内核本质上也是一种应用程序;作用是来控制计算机的硬件资源的,比如说控制硬盘、那么还有可能控制内存等控制网络等相关的一些硬件设备比如说网卡、声卡、键盘、鼠标等);


所有进程的初始运行都位于用户控件,此时即为用户的运行状态(简称:用户态),但是当引用程序需要调用一些硬件资源,比如io操作,需要内核去运行操作,这时候称为进程处于内核运行态(简称:内核态)


系统调用的过程可以简单理解为:


1、 用户态程序将一些数据值放在寄存器中,或者使用参数创建一个堆栈,以此表明需要操作系统提供的服务。

2、 用户态程序执行系统调用。

3、 CPU切换到内核态,并跳到 位于内存指定位置 的指令。

4、 系统调用处理器(system call handler)会读取程序放入内存的数据参数,并执行程序请求的服务。

5、 系统调用完成后,操作系统会重置CPU为用户态并返回系统调用的结果。


再Monitor上锁到锁释放可以看到 ObjectMonitor 的函数调用中 会涉及到 Atomic::cmpxchg_ptr,Atomic::inc_ptr等内核函数,执行同步代码块,没有竞争到锁的对象会执行调用park()被挂起,竞争到锁的线程执行完成退出同步代码块时(即当其他线程退出同步代码块时)会调用unpark()唤醒上次那些没有竞争到锁从而被park()挂起的线程;

相关文章
|
1月前
|
存储 安全 Java
jvm 锁的 膨胀过程?锁内存怎么变化的
【10月更文挑战第3天】在Java虚拟机(JVM)中,`synchronized`关键字用于实现同步,确保多个线程在访问共享资源时的一致性和线程安全。JVM对`synchronized`进行了优化,以适应不同的竞争场景,这种优化主要体现在锁的膨胀过程,即从偏向锁到轻量级锁,再到重量级锁的转变。下面我们将详细介绍这一过程以及锁在内存中的变化。
37 4
|
3月前
|
安全 前端开发 Java
【JVM的秘密揭秘】深入理解类加载器与双亲委派机制的奥秘!
【8月更文挑战第25天】在Java技术栈中,深入理解JVM类加载机制及其双亲委派模型是至关重要的。JVM类加载器作为运行时系统的关键组件,负责将字节码文件加载至内存并转换为可执行的数据结构。其采用层级结构,包括引导、扩展、应用及用户自定义类加载器,通过双亲委派机制协同工作,确保Java核心库的安全性与稳定性。本文通过解析类加载器的分类、双亲委派机制原理及示例代码,帮助读者全面掌握这一核心概念,为开发更安全高效的Java应用程序奠定基础。
92 0
|
1月前
|
存储 监控 算法
美团面试:说说 G1垃圾回收 底层原理?说说你 JVM 调优的过程 ?
尼恩提示: G1垃圾回收 原理非常重要, 是面试的重点, 大家一定要好好掌握
美团面试:说说 G1垃圾回收 底层原理?说说你 JVM 调优的过程  ?
|
4月前
|
缓存 安全 算法
Java面试题:如何通过JVM参数调整GC行为以优化应用性能?如何使用synchronized和volatile关键字解决并发问题?如何使用ConcurrentHashMap实现线程安全的缓存?
Java面试题:如何通过JVM参数调整GC行为以优化应用性能?如何使用synchronized和volatile关键字解决并发问题?如何使用ConcurrentHashMap实现线程安全的缓存?
49 0
|
1月前
|
缓存 前端开发 Java
JVM知识体系学习二:ClassLoader 类加载器、类加载器层次、类过载过程之双亲委派机制、类加载范围、自定义类加载器、编译器、懒加载模式、打破双亲委派机制
这篇文章详细介绍了JVM中ClassLoader的工作原理,包括类加载器的层次结构、双亲委派机制、类加载过程、自定义类加载器的实现,以及如何打破双亲委派机制来实现热部署等功能。
48 3
|
1月前
|
存储 安全 Java
JVM锁的膨胀过程与锁内存变化解析
在Java虚拟机(JVM)中,锁机制是确保多线程环境下数据一致性和线程安全的重要手段。随着线程对共享资源的竞争程度不同,JVM中的锁会经历从低级到高级的膨胀过程,以适应不同的并发场景。本文将深入探讨JVM锁的膨胀过程,以及锁在内存中的变化。
43 1
|
1月前
|
存储 Kubernetes 架构师
阿里面试:JVM 锁内存 是怎么变化的? JVM 锁的膨胀过程 ?
尼恩,一位经验丰富的40岁老架构师,通过其读者交流群分享了一系列关于JVM锁的深度解析,包括偏向锁、轻量级锁、自旋锁和重量级锁的概念、内存结构变化及锁膨胀流程。这些内容不仅帮助群内的小伙伴们顺利通过了多家一线互联网企业的面试,还整理成了《尼恩Java面试宝典》等技术资料,助力更多开发者提升技术水平,实现职业逆袭。尼恩强调,掌握这些核心知识点不仅能提高面试成功率,还能在实际工作中更好地应对高并发场景下的性能优化问题。
|
2月前
|
Arthas Java 测试技术
JVM —— 类加载器的分类,双亲委派机制
类加载器的分类,双亲委派机制:启动类加载器、扩展类加载器、应用程序类加载器、自定义类加载器;JDK8及之前的版本,JDK9之后的版本;什么是双亲委派模型,双亲委派模型的作用,如何打破双亲委派机制
JVM —— 类加载器的分类,双亲委派机制
|
1月前
|
前端开发 Java 应用服务中间件
JVM进阶调优系列(1)类加载器原理一文讲透
本文详细介绍了JVM类加载机制。首先解释了类加载器的概念及其工作原理,接着阐述了四种类型的类加载器:启动类加载器、扩展类加载器、应用类加载器及用户自定义类加载器。文中重点讲解了双亲委派机制,包括其优点和缺点,并探讨了打破这一机制的方法。最后,通过Tomcat的实际应用示例,展示了如何通过自定义类加载器打破双亲委派机制,实现应用间的隔离。
|
3月前
|
开发者 C# Windows
WPF布局大揭秘:掌握布局技巧,轻松创建响应式用户界面,让你的应用程序更上一层楼!
【8月更文挑战第31天】在现代软件开发中,响应式用户界面至关重要。WPF(Windows Presentation Foundation)作为.NET框架的一部分,提供了丰富的布局控件和机制,便于创建可自动调整的UI。本文介绍WPF布局的基础概念与实现方法,包括`StackPanel`、`DockPanel`、`Grid`等控件的使用,并通过示例代码展示如何构建响应式布局。了解这些技巧有助于开发者优化用户体验,适应不同设备和屏幕尺寸。
94 0