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()挂起的线程;

相关文章
|
12月前
|
Oracle Java 关系型数据库
JVM深入原理(一+二):JVM概述和JVM功能
JVM全称是Java Virtual Machine-Java虚拟机JVM作用:本质上是一个运行在计算机上的程序,职责是运行Java字节码文件,编译为机器码交由计算机运行。
301 0
|
12月前
|
Arthas 存储 Java
JVM深入原理(三+四):JVM组成和JVM字节码文件
目录3. JVM组成3.1. 组成-运行时数据区3.2. 组成-类加载器3.3. 组成-执行引擎3.4. 组成-本地接口4. JVM字节码文件4.1. 字节码文件-组成4.1.1. 组成-基础信息4.1.1.1. 基础信息-魔数4.1.1.2. 基础信息-主副版本号4.1.2. 组成-常量池4.1.3. 组成-方法4.1.3.1. 方法-工作流程4.1.4. 组成-字段4.1.5. 组成-属性4.2. 字节码文件-查看工具4.2.1. javap4.2.2. jclasslib4.2.3. 阿里Arthas
207 0
|
12月前
|
存储 安全 Java
JVM深入原理(五):JVM组成和JVM字节码文件
类的生命周期概述:类的生命周期描述了一个类加载,使用,卸载的整个过类的生命周期阶段:类的声明周期主要分为五个阶段:加载->连接->初始化->使用->卸载,其中连接中分为三个小阶段验证->准备->解析。
208 0
|
12月前
|
Arthas Java 测试技术
JVM深入原理(六)(一):JVM类加载器
目录6. JVM类加载器6.1. 类加载器-概述6.2. 类加载器-执行流程6.3. 类加载器-分类(JDK8)6.3.1. JVM底层实现的类加载器6.3.1.1. 启动类加载器6.3.2. Java代码实现类的加载器6.3.2.1. 扩展类加载器6.3.2.2. 应用程序类加载器6.4. 类加载器-Arthas查看类加载器
223 0
|
12月前
|
Java 关系型数据库 MySQL
JVM深入原理(六)(二):双亲委派机制
自定义类加载器打破双亲委派机制的方法:复写ClassLoader中的loadClass方法常见问题:要加载的类名如果是以java.开头,则会抛出安全性异常加载自定义的类都会有一个共同的父类Object,需要在代码中交由父类加载器去加载自定义类加载器不手动指定parent会默认指定应用类加载两个自定义类加载器加载同一个类会被认为是两个对象,只有相同的类加载器+想通的类限定名才会被认为是一个对象。
380 0
|
12月前
|
存储 安全 Java
JVM深入原理(七)(一):运行时数据区
栈的介绍:Java虚拟机栈采用栈的数据结构来管理方法调用中的基本数据,先进后出,每一个方法的调用使用一个栈帧来保存栈的组成:栈:一个线程运行所需要的内存空间,一个栈由多个栈帧组成栈帧:一个方法运行所需要的内存空间活动栈帧:一个线程中只能有一个活动栈帧栈的生命周期:栈随着线程的创建而创建,而回收会在线程销毁时进行栈的执行流程:栈帧压入栈内执行方法执行完毕释放内存若方法间存在调用,那么会压入被调用方法入栈,执行完后释放内存,再执行当前方法,直到执行完毕,释放所有内存。
247 0
|
12月前
|
存储 缓存 安全
JVM深入原理(七)(二):运行时数据区
堆的作用:存放对象的内存空间,它是空间最大的一块内存区域.栈上的局部变量表中,可以存放堆上对象的引用。静态变量也可以存放堆对象的引用,通过静态变量就可以实现对象在线程之间共享。堆的特点:线程共享:堆中的对象都需要考虑线程安全的问题垃圾回收:堆有垃圾回收机制,不再引用的对象就会被回收方法区的概述:方法区是存放基础信息的位置,线程共享,主要包括:类的元信息:保存了所有类的基本信息运行时常量池:保存了字节码文件中的常量池内容静态常量池:字节码文件通过编号查表的方式找到常量。
184 0
|
12月前
|
缓存 算法 Java
JVM深入原理(八)(一):垃圾回收
弱引用-作用:JVM中使用WeakReference对象来实现软引用,一般在ThreadLocal中,当进行垃圾回收时,被弱引用对象引用的对象就直接被回收.软引用-作用:JVM中使用SoftReference对象来实现软引用,一般在缓存中使用,当程序内存不足时,被引用的对象就会被回收.强引用-作用:可达性算法描述的根对象引用普通对象的引用,指的就是强引用,只要有这层关系存在,被引用的对象就会不被垃圾回收。引用计数法-缺点:如果两个对象循环引用,而又没有其他的对象来引用它们,这样就造成垃圾堆积。
284 0
|
12月前
|
算法 Java 对象存储
JVM深入原理(八)(二):垃圾回收
Java垃圾回收过程会通过单独的GC线程来完成,但是不管使用哪一种GC算法,都会有部分阶段需要停止所有的用户线程。这个过程被称之为StopTheWorld简称STW,如果STW时间过长则会影响用户的使用。一般来说,堆内存越大,最大STW就越长,想减少最大STW,就会减少吞吐量,不同的GC算法适用于不同的场景。分代回收算法将整个堆中的区域划分为新生代和老年代。--超过新生代大小的大对象会直接晋升到老年代。
283 0
|
Rust 安全 Java
JVM原理与实现——Synchronized关键字
在多线程Java程序中,`Synchronized`关键字用于确保线程安全。本文深入探讨其工作原理,通过分析字节码`monitorenter`和`monitorexit`,解释JVM如何实现同步机制。文章展示了`Synchronized`方法的编译结果,并详细解析了轻量锁和重度锁的实现过程,包括Mark Word的状态变化及CAS操作的应用。最后简要介绍了`ObjectMonitor::enter()`函数在获取重度锁时的作用。
322 0
JVM原理与实现——Synchronized关键字