博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
synchronized底层实现及锁的升级、降级
阅读量:2167 次
发布时间:2019-05-01

本文共 5707 字,大约阅读时间需要 19 分钟。

锁的升级、降级

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

jvm 锁切换策略

  • 当无竟态条件出现时,默认是偏斜锁。jvm会使用CAS (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里,会发现不仅仅是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继承关系

  • 上述显示,这些锁都未实现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
    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(); } } // … }

转载地址:http://uhjzb.baihongyu.com/

你可能感兴趣的文章
Shell 和Python的区别。
查看>>
Python 列表(list)、字典(dict)、字符串(string)常用基本操作小结
查看>>
Loadrunner之https协议录制回放报错如何解决?(九)
查看>>
python中xrange和range的异同
查看>>
列表、元组、集合、字典
查看>>
【Python】easygui小甲鱼
查看>>
【Python】关于Python多线程的一篇文章转载
查看>>
【Pyton】【小甲鱼】文件
查看>>
【Pyton】【小甲鱼】永久存储:腌制一缸美味的泡菜
查看>>
【Pyton】【小甲鱼】异常处理:你不可能总是对的
查看>>
APP性能测试工具
查看>>
【Pyton】【小甲鱼】类和对象
查看>>
压力测试工具JMeter入门教程
查看>>
作为一名软件测试工程师,需要具备哪些能力
查看>>
【Pyton】【小甲鱼】类和对象:一些相关的BIF(内置函数)
查看>>
【Pyton】【小甲鱼】魔法方法
查看>>
单元测试需要具备的技能和4大阶段的学习
查看>>
【Loadrunner】【浙江移动项目手写代码】代码备份
查看>>
Python几种并发实现方案的性能比较
查看>>
[Jmeter]jmeter之脚本录制与回放,优化(windows下的jmeter)
查看>>