zoukankan      html  css  js  c++  java
  • 锁:synchronized原理

    1、反汇编方式理解synchronized原理

    (1)源码

    public class Test {
        private static Object obj = new Object();
    
        public static void main(String[] args) {
            synchronized (obj) {
                System.out.println("1");
            }
        }
    
        public synchronized void test() {
            System.out.println("a");
        }
    }

    (2)反汇编查看字节码指令

     在monitorenter和monitorexit之间执行的是代码逻辑

    (3)monitorenter

      每一个对象都会和一个监视器monitor关联。监视器被占用时会被锁住,其他线程无法来获取该monitor。 当JVM执行某个线程的某个方法内部的monitorenter时,它会尝试去获取当前对象对应的monitor的所有权。其过程如下:

    • 若monior的进入数为0,线程可以进入monitor,并将monitor的进入数置为1。当前线程成为monitor的owner(所有者)
    • 若线程已拥有monitor的所有权,允许它重入monitor,则进入monitor的进入数加1
    • 若其他线程已经占有monitor的所有权,那么当前尝试获取monitor的所有权的线程会被阻塞,直到monitor的进入数变为0,才能重新尝试获取monitor的所有权。

      synchronized的锁对象会关联一个monitor,这个monitor不是我们主动创建的,是JVM的线程执行到这个同步代码块,发现锁对象没有monitor就会创建monitor,monitor内部有两个重要的成员变量:owner拥有这把锁的线程,recursions会记录线程拥有锁的次数,当一个线程拥有monitor后其他线程只能等待

    (4)monitorexit

      能执行monitorexit指令的线程一定是拥有当前对象的monitor的所有权的线程。

    执行monitorexit时会将monitor的进入数减1。当monitor的进入数减为0时,当前线程退出monitor,不再拥有monitor的所有权,此时其他被这个monitor阻塞的线程可以尝试去获取这个

    2、查看JVM源码

    (1)下载源代码(因为synchronized的源代码是c++写的)

     选择版本:

     

     选择格式:

     (2)查看源代码

    在HotSpot虚拟机中,monitor是由ObjectMonitor实现的。其源码是用c++来实现的,位于HotSpot虚拟机源码ObjectMonitor.hpp文件中(src/share/vm/runtime/objectMonitor.hpp)。ObjectMonitor主要数据结构如下:

    ObjectMonitor() {
      _header    = NULL;
      _count     = 0;
      _waiters    = 0,
      _recursions  = 0;  // 线程的重入次数
    _object    = NULL; // 存储该monitor的对象
      _owner     = NULL; // 标识拥有该monitor的线程
      _WaitSet    = NULL; // 处于wait状态的线程,会被加入到_WaitSet
      _WaitSetLock  = 0 ;
      _Responsible  = NULL;
      _succ     = NULL;
      _cxq      = NULL; // 多线程竞争锁时的单向列表
      FreeNext    = NULL;
      _EntryList   = NULL; // 处于等待锁block状态的线程,会被加入到该列表
      _SpinFreq   = 0;
      _SpinClock   = 0;
      OwnerIsThread = 0;
    }

    •  _owner:初始时为NULL。当有线程占有该monitor时,owner标记为该线程的唯一标识。当线程释放monitor时,owner又恢复为NULL。owner是一个临界资源,JVM是通过CAS操作来保证其线程安全的。
    •  _cxq:竞争队列,所有请求锁的线程首先会被放在这个队列中(单向链接)。_cxq是一个临界资源,JVM通过CAS原子指令来修改_cxq队列。修改前_cxq的旧值填入了node的next字段,_cxq指向新值(新线程)。因此_cxq是一个后进先出的stack(栈)。
    • _EntryList:_cxq队列中有资格成为候选资源的线程会被移动到该队列中。
    • _WaitSet:因为调用wait方法而被阻塞的线程会被放在该队列中。

    3、monitor竞争

    执行monitorenter时,会调用InterpreterRuntime.cpp(位于:src/share/vm/interpreter/interpreterRuntime.cpp) 的 InterpreterRuntime::monitorenter函数。具体代码可参见HotSpot源码

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

    对于重量级锁,monitorenter函数中会调用 ObjectSynchronizer::slow_enter最终调用 ObjectMonitor::enter(位于: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字段设置为当前线程
     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++
     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 ;
    }
     // 省略一些代码
     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 suspended 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);
    }
    • 通过CAS尝试把monitor的owner字段设置为当前线程。
    • 如果设置之前的owner指向当前线程,说明当前线程再次进入monitor,即重入锁,执行recursions ++ ,记录重入的次数。
    • 如果当前线程是第一次进入该monitor,设置recursions为1,_owner为当前线程,该线程成功获得锁并返回。
    • 如果获取锁失败,则等待锁的释放

    4、monitor等待

    竞争失败等待调用的是ObjectMonitor对象的EnterI方法(位于:src/share/vm/runtime/objectMonitor.cpp),源码如下所示:

    void ATTR ObjectMonitor::EnterI (TRAPS) {
      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 (_owner == Self    , "invariant") ;
        assert (_succ != 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 ;
      for (;;) {
        node._next = nxt = _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.
        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, "invariant") ;
        if ((SyncFlags & 2) && _Responsible == NULL) {
         Atomic::cmpxchg_ptr (Self, &_Responsible, NULL) ;
       }
        // park self
        if (_Responsible == Self || (SyncFlags & 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将当前线程挂起(由于用户和系统的需要,例如,终端用户需要暂停程序研究其执行情况或对其进行修改、OS为了提
    高内存利用率需要将暂时不能运行的进程(处于就绪或阻塞队列的进程)调出到磁盘),等待被唤醒
          Self->_ParkEvent->park() ;    }     if (TryLock(Self) > 0) break ;     // 省略部分代码  }   // 省略部分代码 }

    当该线程被唤醒时,会从挂起的点继续执行,通过 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 free momentarily, but we lost the race to the lock.
       // Interference -- the CAS failed.
       // We can either return -1 or retry.
       // Retry doesn't make as much sense because the lock was just acquired.
       if (true) return -1 ;
     }
    }
    • 当前线程被封装成ObjectWaiter对象node,状态设置成ObjectWaiter::TS_CXQ。
    • 在for循环中,通过CAS把node节点push到_cxq列表中,同一时刻可能有多个线程把自己的node节点push到_cxq列表中。
    • node节点push到_cxq列表之后,通过自旋尝试获取锁,如果还是没有获取到锁,则通过park将当前线程挂起,等待被唤醒。
    • 当该线程被唤醒时,会从挂起的点继续执行,通过 ObjectMonitor::TryLock 尝试获取锁。

    5、monitor释放

    • 当某个持有锁的线程执行完同步代码块时,会进行锁的释放,给其它线程机会执行同步代码,在HotSpot中,通过退出monitor的方式实现锁的释放,并通知被阻塞的线程,具体实现位于ObjectMonitor的exit方法中。(位于:src/share/vm/runtime/objectMonitor.cpp)
    • 退出同步代码块时会让_recursions减1,当_recursions的值减为0时,说明线程释放了锁。
    • 根据不同的策略(由QMode指定),从cxq或EntryList中获取头节点,通过ObjectMonitor::ExitEpilog 方法唤醒该节点封装的线程,唤醒操作最终由unpark完成
    • 被唤醒的线程,会回到 void ATTR ObjectMonitor::EnterI (TRAPS) 的第600行,继续执行monitor的竞争。

    6、monitor是重量级锁

      可以看到ObjectMonitor的函数调用中会涉及到Atomic::cmpxchg_ptr,Atomic::inc_ptr等内核函数,执行同步代码块,没有竞争到锁的对象会park()被挂起,竞争到锁的线程会unpark()唤醒。这个时候就会存在操作系统用户态和内核态的转换,这种切换会消耗大量的系统资源。所以synchronized是Java语言中是一个重量级(Heavyweight)的操作。

    内核:可以理解为一种软件,控制计算机的硬件资源,并提供上层应用程序运行的环境。
    用户空间:上层应用程序活动的空间。应用程序的执行必须依托于内核提供的资源,包括CPU资源、存储资源、I/O资源等。
    系统调用:为了使上层应用能够访问到这些资源,内核必须为上层应用提供访问的接口:即系统调用。

    用户态与内核态的划分主要是为了保护系统,用户态程序的崩溃不会影响内核

    所有进程初始都运行于用户空间,此时即为用户运行状态(简称:用户态);但是当它调用系统调用执行某些操作时,例如 I/O调用,此时需要陷入内核中运行,我们就称进程处于内核运行态(或简称为内核态)。 系统调用的过程可以简单理解为:

    • 用户态程序将一些数据值放在寄存器中, 或者使用参数创建一个堆栈, 以此表明需要操作系统提供的服务。
    • 用户态程序执行系统调用。
    • CPU切换到内核态,并跳到位于内存指定位置的指令。
    • 系统调用处理器(system call handler)会读取程序放入内存的数据参数,并执行程序请求的服务。
    • 系统调用完成后,操作系统会重置CPU为用户态并返回系统调用的结果。

      由此可见用户态切换至内核态需要传递许多变量,同时内核还需要保护好用户态在切换时的一些寄存器值、变量等,以备内核态切换回用户态。这种切换就带来了大量的系统资源消耗,这就是在synchronized未优化之前,效率低的原因。

    每个人都会有一段异常艰难的时光 。 生活的压力 , 工作的失意 , 学业的压力。 爱的惶惶不可终日。 挺过来的 ,人生就会豁然开朗。 挺不过来的 ,时间也会教你 ,怎么与它们握手言和 ,所以不必害怕的。 ——杨绛
  • 相关阅读:
    站立会议01---个人总结
    团队项目的NABCD
    查找水王
    《构建之法》读书笔记03
    《构建之法》读书笔记02
    《构建之法》读书笔记01
    Java web应用开发技术
    Java 模拟ATM(修正)
    Java 多态
    Java 接口与继承 道至简第六章发表阅读笔记
  • 原文地址:https://www.cnblogs.com/zhai1997/p/13544523.html
Copyright © 2011-2022 走看看