飞道的博客

synchronized底层实现及锁的升级、降级

448人阅读  评论(0)

锁的升级、降级

  • 所谓所的升级、降级,就是JVM 优化synchronized的运行机制,当jvm检测到不同的竟态条件,会自动切换到合适的锁实现,这种切换情况就是锁的升级、降级
  • synchronized代码块有一对monitorenter/monitorenter指令实现,monitor对象是同步实现的基本单元
  • java6之前,monitor实现完全依靠操作系统内部的互斥锁,因为需要完成用户态到内核态的切换,所以同步操作是一个无差别的重量级操作
  • 现代(oracle)JDK 中,JVM 对此进行重大改进,提供欧冠你三种不同monitor实现,也就是常说的三种不同锁,偏斜锁(Biased Locking),轻量级锁和重量级所,根据不同竟态条件去选择不同锁

jvm 锁切换策略

  • 当无竟态条件出现时,默认是偏斜锁。jvm会使用JVM (compare and swap)操作,在对象头上的Mark Word位置 设置线程ID ,以表示这个对象偏向于当前线程,并不涉及真正的互斥锁。这样做的假设适用多数应用场景(竟态条件),因为大部分对象生命周期内最多会被一个线程锁定。
  • 如果有另外线程试图锁定某个已经被偏斜过的对象,JVM 就需要撤销(revoke)偏斜锁,并切换到轻量级锁实现。轻量级锁依赖CAS 操作Mark Word来试图获取锁。若重试成功,就使用轻量级级锁;否则进一步升级为重量级锁。
  • JVM在进入安全点(safepoint)时,会检查是否有闲置Monitor ,然后试图进行降级。

知识扩展

JVM synchronized源码分析

  • synchronized是JVM 内部的Instrinsic Lock。所以偏斜锁、轻量级锁、重量级锁的代码实现不在核心类库部分,而是JVM 代码中。

  • java代码运行分两种模式,解释型: javac经编译成字节码后,运行时字节码被jvm加载、解释为机器码进行解释执行。编译型:javac编译成字节码后,jvm的JIT(java 即时编译器)会根据jvm代码情况,执行效率使用内联、虚化等规则,在运行时会将热点代码编译成机器码进行优化,提高执行效率

    1.[解释器版本-最新代码](http://hg.openjdk.java.net/jdk/jdk/file/6659a8f57d78/src/hotspot/share/interpreter/interpreterRuntime.cpp)
    2.为便于理解,这里专注于[通用基类-最新代码](http://hg.openjdk.java.net/jdk/jdk/file/6659a8f57d78/src/hotspot/share/runtime/)实现
    

    偏斜锁代码分析

    • 1.首先,synchronized行为是JVM runtime的一部分,所以我们需要先找到Runtime相关功能实现,通过在代码搜索“monitor_enter”或”monitor Enter”,很直观的就可以定位到:
      a.解释器和编译器运行时的基类:sharedRuntime.cpp/hpp,UseBiasedLocking是一个检查,因为,在JVM 启动时,我们可以指定是否开启偏斜锁。偏斜锁不适合所有场景,撤销(revoke)是比较重的行为,只有当存在较多真正竞争的synchronized块时,才能体现出明显改善。
      
      	Handle h_obj(THREAD, obj);
      		if (UseBiasedLocking) {
      		 // Retry fast entry if bias is revoked to avoid unnecessary inflation
      					ObjectSynchronizer::fast_enter(h_obj, lock, true, CHECK);
      		  } else {
      				 ObjectSynchronizer::slow_enter(h_obj, lock, CHECK);
      			 }
      b.另一方面,偏斜锁会延缓JIT 预热进程,因此大多数性能测试会显式地关闭偏斜锁
      			命令:开启 -XX:+UseBiasedLocking 关闭:-XX:+UseBiasedLocking
      
    • 2.faster_enter是我们熟悉的完整锁的获取路径,slow_enter则是绕过偏斜锁,直接进入轻量级所获取逻辑,核心实现逻辑如下
      (1)类似faster_enter实现,解释器或动态编译器,都是拷贝这的基础逻辑,所以如果我们修改这个部分逻辑,要保证一致性。修改要谨慎,微小问题都可能造成死锁或正确性问题。
      (2)逻辑如下:bisedLocking定义了偏斜锁的相关操作,revoke_and_rebias是获取偏斜锁的入口方法,revoke_at_safepoint则定义了当检测到安全点是的处理逻辑;如果获取偏斜锁失败,则进入slow_enter;该方法同样检查是否开启偏斜锁,但从代码看,如果关闭偏斜锁,不会进入该方法,所以其是个额外保障性检查
      (3)核心代码如下:
      	void ObjectSynchronizer::fast_enter(Handle obj, BasicLock* lock,
      	                                  bool attempt_rebias, TRAPS) {
      	  if (UseBiasedLocking) {
      	    if (!SafepointSynchronize::is_at_safepoint()) {
      	      BiasedLocking::Condition cond = BiasedLocking::revoke_and_rebias(obj, attempt_rebias, THREAD);
      	      if (cond == BiasedLocking::BIAS_REVOKED_AND_REBIASED) {
      	        return;
      	      }
      	  } else {
      	      assert(!attempt_rebias, "can not rebias toward VM thread");
      	      BiasedLocking::revoke_at_safepoint(obj);
      	  }
      	    assert(!obj->mark()->has_bias_pattern(), "biases should be revoked by now");
      	  }
      	 
      	  slow_enter(obj, lock, THREAD);
      	}
      
    • 3.JVM同步相关的各种逻辑:synchronized.cpp/hpp
      	仔细查看 synchronized.cpp里,会发现不仅仅是synchronized的逻辑,包括从本地代码(JNI),出发Monitor 动作,都可以看到
      
    • 4.对象头的Mark Word
    • 5.随着线程竞争加剧,偏斜锁升级为轻量级锁,即slow_enter
      	流程:设置 Displaced Header,然后利用cas_set_mark设置对象Mark Word,如果成功就获取轻量级锁;否则Displaced Header,然后进入锁膨胀阶段,具体实现在inflated方法中,
      	void ObjectSynchronizer::slow_enter(Handle obj, BasicLock* lock, TRAPS) {
      	  markOop mark = obj->mark();
      	 if (mark->is_neutral()) {
      	       // 将目前的Mark Word复制到Displaced Header上
      	  lock->set_displaced_header(mark);
      	  // 利用CAS设置对象的Mark Word
      	    if (mark == obj()->cas_set_mark((markOop) lock, mark)) {
      	      TEVENT(slow_enter: release stacklock);
      	      return;
      	    }
      	    // 检查存在竞争
      	  } else if (mark->has_locker() &&
      	             THREAD->is_lock_owned((address)mark->locker())) {
      	  // 清除
      	    lock->set_displaced_header(NULL);
      	    return;
      	  }
      	 
      	  // 重置Displaced Header
      	  lock->set_displaced_header(markOopDesc::unused_mark());
      	  ObjectSynchronizer::inflate(THREAD,
      	                            obj(),
      	                              inflate_cause_monitor_enter)->enter(THREAD);
      	}
      
      
    
    - 6。deflate_idle_monitors(参考源码是分析锁降级逻辑的入口,这部分行为会进行持续改进,因为其逻辑实在安全点内进行,处理不当会托长JVM停顿时间(STW,stop-the-world)的时间
    -  7. fast_exit或slow_exit是对应的锁释放逻辑(参考源码)
    
    

Lock设计

  • 类图

  • 上述显示,这些锁都未实现Lock接口。ReadWriteLock是一个单独接口,通常代表一对锁(共享读锁,互斥写锁)。标准库提供了再入版本的读写锁(ReentrantReadWriteLock),对应的语义同ReentrantLock相似。

  • StampedLock也是单独类型,其不支持再入性予以,即不是以持有锁的线程为单位。其在提供类似读写锁的同时,还支持基于假设的优化读模式。逻辑是先试着读,然后通过validate方法确认是否进入写模式,如果未进入,则成功避免开销;如果进入,则尝试获取读锁。例子如下

    public class StampedSample {
      private final StampedLock sl = new StampedLock();
    
      void mutate() {
          long stamp = sl.writeLock();
          try {
              write();
          } finally {
              sl.unlockWrite(stamp);
          }
      }
    
      Data access() {
          long stamp = sl.tryOptimisticRead();
          Data data = read();
          if (!sl.validate(stamp)) {
              stamp = sl.readLock();
              try {
                  data = read();
              } finally {
                  sl.unlockRead(stamp);
              }
          }
          return data;
      }
      // …
    }
    
  • 读写锁的需求场景

    1.ReentrantLock和synchronized简单实用,但行为有一定局限性,通俗说就是”霸道”,要么不占用,要么独占(写时独占保证数据一致性,读时独占则严重影响线程并发).实际场景,不需要大量竞争的写操作,而是以并发读取为主。因此出现ReentrantReadWriteLock等
    2.java并发包提供的读写锁扩展了锁的能力,其原理是多个读操作不需要互斥,读操作不会更改数据,不存在相互干扰,写操作则会导致并发一致性问题。因此,写线程之间、读写线程之间,需要精心设计互斥逻辑
    3.以下是使用ReentrantReadWriteLock,解决数据量打,并发读多,并发写少,能够比纯同步版本凸显优势。运行过程中,读锁试图锁定时,写锁是被某个线程持有,读锁则无法获取,只能等待对方操作结束,这样保证不会读取到有争议的数据。读锁可以并发访问。
    4.这里writeLock和unLockWrite一定保证成对调用
    5.例子如下:
    public class RWSample {
      private final Map<String, String> m = new TreeMap<>();
      private final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
      private final Lock r = rwl.readLock();
      private final Lock w = rwl.writeLock();
      public String get(String key) {
          r.lock();
          System.out.println("读锁锁定!");
          try {
              return m.get(key);
          } finally {
              r.unlock();
          }
      }
    
      public String put(String key, String entry) {
          w.lock();
      System.out.println("写锁锁定!");
            try {
                return m.put(key, entry);
            } finally {
                w.unlock();
            }
        }
      // …
      }
    

转载:https://blog.csdn.net/dymkkj/article/details/104957358
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场