搜索
首页Javajava教程Java虚拟机四种优化对内部锁的方式解析

Java虚拟机四种优化对内部锁的方式解析

Oct 13, 2017 am 10:18 AM
java优化内部

这篇文章主要介绍了浅谈Java虚拟机对内部锁的四种优化方式,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

自Java 6/Java 7开始,Java虚拟机对内部锁的实现进行了一些优化。这些优化主要包括锁消除(Lock Elision)、锁粗化(Lock Coarsening)、偏向锁(Biased Locking)以及适应性锁(Adaptive Locking)。这些优化仅在Java虚拟机server模式下起作用(即运行Java程序时我们可能需要在命令行中指定Java虚拟机参数“-server”以开启这些优化)。

1 锁消除

锁消除(Lock Elision)是JIT编译器对内部锁的具体实现所做的一种优化。

 

锁消除(Lock Elision)示意图

在动态编译同步块的时候,JIT编译器可以借助一种被称为逃逸分析(Escape Analysis)的技术来判断同步块所使用的锁对象是否只能够被一个线程访问而没有被发布到其他线程。如果同步块所使用的锁对象通过这种分析被证实只能够被一个线程访问,那么JIT编译器在编译这个同步块的时候并不生成synchronized所表示的锁的申请与释放对应的机器码,而仅生成原临界区代码对应的机器码,这就造成了被动态编译的字节码就像是不包含monitorenter(申请锁)和monitorexit(释放锁)这两个字节码指令一样,即消除了锁的使用。这种编译器优化就被称为锁消除(Lock Elision),它使得特定情况下我们可以完全消除锁的开销。

Java标准库中的有些类(比如StringBuffer)虽然是线程安全的,但是在实际使用中我们往往不在多个线程间共享这些类的实例。而这些类在实现线程安全的时候往往借助于内部锁。因此,这些类是锁消除优化的常见目标。

清单12-1  可进行锁消除优化的示例代码


public class LockElisionExample {

 public static String toJSON(ProductInfo productInfo) {
  StringBuffer sbf = new StringBuffer();
  sbf.append("{\"productID\":\"").append(productInfo.productID);
  sbf.append("\",\"categoryID\":\"").append(productInfo.categoryID);
  sbf.append("\",\"rank\":").append(productInfo.rank);
  sbf.append(",\"inventory\":").append(productInfo.inventory);
  sbf.append('}');

  return sbf.toString();
 }
}

在上面例子中,JIT编译器在编译toJSON方法的时候会将其调用的StringBuffer.append/toString方法内联(Inline)到该方法之中,这相当于把StringBuffer.append/toString方法的方法体中的指令复制到toJSON方法体之中。这里的StringBuffer实例sbf是一个局部变量,并且该变量所引用的对象并没有被发布到其他线程,因此sbf引用的对象只能够被sbf所在的方法(toJSON方法)的当前执行线程(一个线程)访问。所以,JIT编译器此时可以消除toJSON方法中从StringBuffer.append/toString方法的方法体复制的指令所使用的内部锁。在这个例子中,StringBuffer.append/toString方法本身所使用的锁并不会被消除,因为系统中可能还有其他地方在使用StringBuffer,而这些代码可能会共享StringBuffer实例。

锁消除优化所依赖的逃逸分析技术自Java SE 6u23起默认是开启的,但是锁消除优化是在Java 7开始引入的。

从上述例子可以看出,锁消除优化还可能需要以JIT编译器的内联优化为前提。而一个方法是否会被JIT编译器内联取决于该方法的热度以及该方法对应的字节码的尺寸(Bytecode Size)。因此,锁消除优化能否被实施还取决于被调用的同步方法(或者带同步块的方法)是否能够被内联。

锁消除优化告诉我们在该使用锁的情况下必须使用锁,而不必过多在意锁的开销。开发人员应该在代码的逻辑层面考虑是否需要加锁,而至于代码运行层面上某个锁是否真的有必要使用则由JIT编译器来决定。锁消除优化并不表示开发人员在编写代码的时候可以随意使用内部锁(在不需要加锁的情况下加锁),因为锁消除是JIT编译器而不是javac所做的一种优化,而一段代码只有在其被执行的频率足够大的情况下才有可能会被JIT编译器优化。也就是说在JIT编译器优化介入之前,只要源代码中使用了内部锁,那么这个锁的开销就会存在。另外,JIT编译器所执行的内联优化、逃逸分析以及锁消除优化本身都是有其开销的。

在锁消除的作用下,利用ThreadLocal将一个线程安全的对象(比如Random)作为一个线程特有对象来使用,不仅仅可以避免锁的争用,还可以彻底消除这些对象内部所使用的锁的开销。

2 锁粗化

锁粗化(Lock Coarsening/Lock Merging)是JIT编译器对内部锁的具体实现所做的一种优化。

 

锁粗化(Lock Coarsening)示意图

对于相邻的几个同步块,如果这些同步块使用的是同一个锁实例,那么JIT编译器会将这些同步块合并为一个大同步块,从而避免了一个线程反复申请、释放同一个锁所导致的开销。然而,锁粗化可能导致一个线程持续持有一个锁的时间变长,从而使得同步在该锁之上的其他线程在申请锁时的等待时间变长。例如上图中,第1个同步块结束和第2个同步块开始之间的时间间隙中,其他线程本来是有机会获得monitorX的,但是经过锁粗化之后由于临界区的长度变长,这些线程在申请monitorX时所需的等待时间也相应变长了。因此,锁粗化不会被应用到循环体内的相邻同步块。

相邻的两个同步块之间如果存在其他语句,也不一定就会阻碍JIT编译器执行锁粗化优化,这是因为JIT编译器可能在执行锁粗化优化前将这些语句挪到(即指令重排序)后一个同步块的临界区之中(当然,JIT编译器并不会将临界区内的代码挪到临界区之外)。

实际上,我们写的代码中可能很少会出现上图中那种连续的同步块。这种同一个锁实例引导的相邻同步块往往是JIT编译器编译之后形成的。

例如,在下面的例子中

清单12-2  可进行锁粗化优化的示例代码


public class LockCoarseningExample {
 private final Random rnd = new Random();

 public void simulate() {
  int iq1 = randomIQ();
  int iq2 = randomIQ();
  int iq3 = randomIQ();
  act(iq1, iq2, iq3);
 }

 private void act(int... n) {
  // ...
 }

 // 返回随机的智商值
 public int randomIQ() {
  // 人类智商的标准差是15,平均值是100
  return (int) Math.round(rnd.nextGaussian() * 15 + 100);
 }
 // ...
}

simulate方法连续调用randomIQ方法来生成3个符合正态分布(高斯分布)的随机智商(IQ)。在simulate方法被执行得足够频繁的情况下,JIT编译器可能对该方法执行一系优化:首先,JIT编译器可能将randomIQ方法内联(inline)到simulate方法中,这相当于把randomIQ方法体中的指令复制到simulate方法之中。在此基础上,randomIQ方法中的rnd.nextGaussian()调用也可能被内联,这相当于把Random.nextGaussian()方法体中的指令复制到simulate方法之中。Random.nextGaussian()是一个同步方法,由于Random实例rnd可能被多个线程共享(因为simulate方法可能被多个线程执行),因此JIT编译器无法对Random.nextGaussian()方法本身执行锁消除优化,这使得被内联到simulate方法中的Random.nextGaussian()方法体相当于一个由rnd引导的同步块。经过上述优化之后,JIT编译器便会发现simulate方法中存在3个相邻的由rnd(Random实例)引导的同步块,于是锁粗化优化便“粉墨登场”了。

锁粗化默认是开启的。如果要关闭这个特性,我们可以在Java程序的启动命令行中添加虚拟机参数“-XX:-EliminateLocks”(开启则可以使用虚拟机参数“-XX:+EliminateLocks”)。

3 偏向锁

偏向锁(Biased Locking)是Java虚拟机对锁的实现所做的一种优化。这种优化基于这样的观测结果(Observation):大多数锁并没有被争用(Contented),并且这些锁在其整个生命周期内至多只会被一个线程持有。然而,Java虚拟机在实现monitorenter字节码(申请锁)和monitorexit字节码(释放锁)时需要借助一个原子操作(CAS操作),这个操作代价相对来说比较昂贵。因此,Java虚拟机会为每个对象维护一个偏好(Bias),即一个对象对应的内部锁第1次被一个线程获得,那么这个线程就会被记录为该对象的偏好线程(Biased Thread)。这个线程后续无论是再次申请该锁还是释放该锁,都无须借助原先(指未实施偏向锁优化前)昂贵的原子操作,从而减少了锁的申请与释放的开销。

然而,一个锁没有被争用并不代表仅仅只有一个线程访问该锁,当一个对象的偏好线程以外的其他线程申请该对象的内部锁时,Java虚拟机需要收回(Revoke)该对象对原偏好线程的“偏好”并重新设置该对象的偏好线程。这个偏好收回和重新分配过程的代价也是比较昂贵的,因此如果程序运行过程中存在比较多的锁争用的情况,那么这种偏好收回和重新分配的代价便会被放大。有鉴于此,偏向锁优化只适合于存在相当大一部分锁并没有被争用的系统之中。如果系统中存在大量被争用的锁而没有被争用的锁仅占极小的部分,那么我们可以考虑关闭偏向锁优化。

偏向锁优化默认是开启的。要关闭偏向锁优化,我们可以在Java程序的启动命令行中添加虚拟机参数“-XX:-UseBiasedLocking”(开启偏向锁优化可以使用虚拟机参数“-XX:+UseBiasedLocking”)。

4 适应性锁

适应性锁(Adaptive Locking,也被称为 Adaptive Spinning )是JIT编译器对内部锁实现所做的一种优化。

存在锁争用的情况下,一个线程申请一个锁的时候如果这个锁恰好被其他线程持有,那么这个线程就需要等待该锁被其持有线程释放。实现这种等待的一种保守方法——将这个线程暂停(线程的生命周期状态变为非Runnable状态)。由于暂停线程会导致上下文切换,因此对于一个具体锁实例来说,这种实现策略比较适合于系统中绝大多数线程对该锁的持有时间较长的场景,这样才能够抵消上下文切换的开销。另外一种实现方法就是采用忙等(Busy Wait)。所谓忙等相当于如下代码所示的一个循环体为空的循环语句:


// 当锁被其他线程持有时一直循环 
while (lockIsHeldByOtherThread){}

可见,忙等是通过反复执行空操作(什么也不做)直到所需的条件成立为止而实现等待的。这种策略的好处是不会导致上下文切换,缺点是比较耗费处理器资源——如果所需的条件在相当长时间内未能成立,那么忙等的循环就会一直被执行。因此,对于一个具体的锁实例来说,忙等策略比较适合于绝大多数线程对该锁的持有时间较短的场景,这样能够避免过多的处理器时间开销。

事实上,Java虚拟机也不是非要在上述两种实现策略之中择其一 ——它可以综合使用上述两种策略。对于一个具体的锁实例,Java虚拟机会根据其运行过程中收集到的信息来判断这个锁是属于被线程持有时间“较长”的还是“较短”的。对于被线程持有时间“较长”的锁,Java虚拟机会选用暂停等待策略;而对于被线程持有时间“较短”的锁,Java虚拟机会选用忙等等待策略。Java虚拟机也可能先采用忙等等待策略,在忙等失败的情况下再采用暂停等待策略。Java虚拟机的这种优化就被称为适应性锁(Adaptive Locking),这种优化同样也需要JIT编译器介入。

适应性锁优化可以是以具体的一个锁实例为基础的。也就是说,Java虚拟机可能对一个锁实例采用忙等等待策略,而对另外一个锁实例采用暂停等待策略。

从适应性锁优化可以看出,内部锁的使用并不一定会导致上下文切换,这就是我们说锁与上下文切换时均说锁“可能”导致上下文切换的原因。

 

以上是Java虚拟机四种优化对内部锁的方式解析的详细内容。更多信息请关注PHP中文网其他相关文章!

声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
是否有任何威胁或增强Java平台独立性的新兴技术?是否有任何威胁或增强Java平台独立性的新兴技术?Apr 24, 2025 am 12:11 AM

新兴技术对Java的平台独立性既有威胁也有增强。1)云计算和容器化技术如Docker增强了Java的平台独立性,但需要优化以适应不同云环境。2)WebAssembly通过GraalVM编译Java代码,扩展了其平台独立性,但需与其他语言竞争性能。

JVM的实现是什么,它们都提供了相同的平台独立性?JVM的实现是什么,它们都提供了相同的平台独立性?Apr 24, 2025 am 12:10 AM

不同JVM实现都能提供平台独立性,但表现略有不同。1.OracleHotSpot和OpenJDKJVM在平台独立性上表现相似,但OpenJDK可能需额外配置。2.IBMJ9JVM在特定操作系统上表现优化。3.GraalVM支持多语言,需额外配置。4.AzulZingJVM需特定平台调整。

平台独立性如何降低发展成本和时间?平台独立性如何降低发展成本和时间?Apr 24, 2025 am 12:08 AM

平台独立性通过在多种操作系统上运行同一套代码,降低开发成本和缩短开发时间。具体表现为:1.减少开发时间,只需维护一套代码;2.降低维护成本,统一测试流程;3.快速迭代和团队协作,简化部署过程。

Java的平台独立性如何促进代码重用?Java的平台独立性如何促进代码重用?Apr 24, 2025 am 12:05 AM

Java'splatformindependencefacilitatescodereusebyallowingbytecodetorunonanyplatformwithaJVM.1)Developerscanwritecodeonceforconsistentbehavioracrossplatforms.2)Maintenanceisreducedascodedoesn'tneedrewriting.3)Librariesandframeworkscanbesharedacrossproj

您如何在Java应用程序中对平台特定问题进行故障排除?您如何在Java应用程序中对平台特定问题进行故障排除?Apr 24, 2025 am 12:04 AM

要解决Java应用程序中的平台特定问题,可以采取以下步骤:1.使用Java的System类查看系统属性以了解运行环境。2.利用File类或java.nio.file包处理文件路径。3.根据操作系统条件加载本地库。4.使用VisualVM或JProfiler优化跨平台性能。5.通过Docker容器化确保测试环境与生产环境一致。6.利用GitHubActions在多个平台上进行自动化测试。这些方法有助于有效地解决Java应用程序中的平台特定问题。

JVM中的类加载程序子系统如何促进平台独立性?JVM中的类加载程序子系统如何促进平台独立性?Apr 23, 2025 am 12:14 AM

类加载器通过统一的类文件格式、动态加载、双亲委派模型和平台无关的字节码,确保Java程序在不同平台上的一致性和兼容性,实现平台独立性。

Java编译器会产生特定于平台的代码吗?解释。Java编译器会产生特定于平台的代码吗?解释。Apr 23, 2025 am 12:09 AM

Java编译器生成的代码是平台无关的,但最终执行的代码是平台特定的。1.Java源代码编译成平台无关的字节码。2.JVM将字节码转换为特定平台的机器码,确保跨平台运行但性能可能不同。

JVM如何处理不同操作系统的多线程?JVM如何处理不同操作系统的多线程?Apr 23, 2025 am 12:07 AM

多线程在现代编程中重要,因为它能提高程序的响应性和资源利用率,并处理复杂的并发任务。JVM通过线程映射、调度机制和同步锁机制,在不同操作系统上确保多线程的一致性和高效性。

See all articles

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

Video Face Swap

Video Face Swap

使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

热工具

SecLists

SecLists

SecLists是最终安全测试人员的伙伴。它是一个包含各种类型列表的集合,这些列表在安全评估过程中经常使用,都在一个地方。SecLists通过方便地提供安全测试人员可能需要的所有列表,帮助提高安全测试的效率和生产力。列表类型包括用户名、密码、URL、模糊测试有效载荷、敏感数据模式、Web shell等等。测试人员只需将此存储库拉到新的测试机上,他就可以访问到所需的每种类型的列表。

PhpStorm Mac 版本

PhpStorm Mac 版本

最新(2018.2.1 )专业的PHP集成开发工具

WebStorm Mac版

WebStorm Mac版

好用的JavaScript开发工具

记事本++7.3.1

记事本++7.3.1

好用且免费的代码编辑器

DVWA

DVWA

Damn Vulnerable Web App (DVWA) 是一个PHP/MySQL的Web应用程序,非常容易受到攻击。它的主要目标是成为安全专业人员在合法环境中测试自己的技能和工具的辅助工具,帮助Web开发人员更好地理解保护Web应用程序的过程,并帮助教师/学生在课堂环境中教授/学习Web应用程序安全。DVWA的目标是通过简单直接的界面练习一些最常见的Web漏洞,难度各不相同。请注意,该软件中