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

相关文章
|
4天前
|
前端开发 Java 开发者
JVM类加载器的分类以及双亲委派机制
JVM类加载器的分类以及双亲委派机制
|
14天前
|
监控 前端开发 安全
JVM工作原理与实战(十四):JDK9及之后的类加载器
JVM作为Java程序的运行环境,其负责解释和执行字节码,管理内存,确保安全,支持多线程和提供性能监控工具,以及确保程序的跨平台运行。本文主要介绍了JDK8及之前的类加载器、JDK9及之后的类加载器等内容。
19 2
|
14天前
|
监控 Java 关系型数据库
JVM工作原理与实战(十三):打破双亲委派机制-线程上下文类加载器
JVM作为Java程序的运行环境,其负责解释和执行字节码,管理内存,确保安全,支持多线程和提供性能监控工具,以及确保程序的跨平台运行。本文主要介绍了打破双亲委派机制的方法、线程上下文类加载器等内容。
14 2
|
14天前
|
监控 安全 前端开发
JVM工作原理与实战(十二):打破双亲委派机制-自定义类加载器
JVM作为Java程序的运行环境,其负责解释和执行字节码,管理内存,确保安全,支持多线程和提供性能监控工具,以及确保程序的跨平台运行。本文主要介绍了打破双亲委派机制的方法、自定义类加载器等内容。
17 1
|
14天前
|
监控 前端开发 安全
JVM工作原理与实战(十一):双亲委派机制
JVM作为Java程序的运行环境,其负责解释和执行字节码,管理内存,确保安全,支持多线程和提供性能监控工具,以及确保程序的跨平台运行。本文主要介绍了双亲委派机制、父类加载器、双亲委派机制的主要作用、双亲委派机制常见问题等内容。
14 1
|
14天前
|
Arthas 安全 Java
JVM工作原理与实战(八):类加载器的分类
JVM作为Java程序的运行环境,其负责解释和执行字节码,管理内存,确保安全,支持多线程和提供性能监控工具,以及确保程序的跨平台运行。本文主要介绍了类加载器、类加载器的分类等内容。
18 4
|
14天前
|
监控 安全 Java
JVM工作原理与实战(七):类的生命周期-初始化阶段
JVM作为Java程序的运行环境,其负责解释和执行字节码,管理内存,确保安全,支持多线程和提供性能监控工具,以及确保程序的跨平台运行。本文主要介绍了类的生命周期、类的初始化阶段等内容。
24 5
|
14天前
|
存储 安全 Java
JVM工作原理与实战(六):类的生命周期-连接阶段
JVM作为Java程序的运行环境,其负责解释和执行字节码,管理内存,确保安全,支持多线程和提供性能监控工具,以及确保程序的跨平台运行。本文主要介绍了类的生命周期、类的连接阶段等内容。
29 4
|
14天前
|
存储 监控 安全
JVM工作原理与实战(五):类的生命周期-加载阶段
JVM作为Java程序的运行环境,其负责解释和执行字节码,管理内存,确保安全,支持多线程和提供性能监控工具,以及确保程序的跨平台运行。本文主要介绍了类的生命周期、类的加载阶段等内容。
24 5
|
14天前
|
Arthas 运维 监控
JVM工作原理与实战(四):字节码常用工具
JVM作为Java程序的运行环境,其负责解释和执行字节码,管理内存,确保安全,支持多线程和提供性能监控工具,以及确保程序的跨平台运行。本文主要介绍了字节码常用工具javap、jclasslib、Arthas等内容。
32 3