岁月联盟 - 技术社区 - BBS.SYUE.COM's Archiver

huxinisme 发表于 2007-2-28 23:31

Java 线程锁定优化策略

信息来源:Flier's Sky  ([url]http://www.blogcn.com/User8/flier_lu/)[/url]
Brian Goetz 最近在其 dw  Java theory and practice 专栏  里发表了几篇有趣的文章,介绍了 Sun HotSpot JVM 在以后几个版本中,对锁定等性能优化的思路。

Synchronization optimizations in Mustang

其中核心的思路是将锁分为 contended 和 uncontended 两类来分别优化。根据 80/20 经验原则和一些实测数据,大多数的线程锁实际上都只是预防性的。例如被广泛使用的 Vector 类中,绝大多数方法都被缺省加上 synchronized 修饰符以避免多线程问题,虽然这能够最大限度降低潜在的问题,但相应付出的代价也是巨大的。因为大量的实际使用都是局部或者不会涉及线程同步情况的,例如下面这种情况:

CODE:
public String getStoogeNames() { Vector v = new Vector(); v.add(\"Moe\"[img]/images/wink.gif[/img]; v.add(\"Larry\"[img]/images/wink.gif[/img]; v.add(\"Curly\"[img]/images/wink.gif[/img]; return v.toString(); }
[Copy to clipboard]
要缓解此问题,一方面可以从使用者角度通过库的选择来避免无效锁定,另一方面则可以由 JVM 根据行为进行锁定优化。
对前者来说,随着 JDK 的发展可以说灵活性大大增强。例如选择性使用 ArrayList 和 Collections.synchronizedList 可以获得类似的能力,但并不用付出不必要的代价;同时也可以使用已成为 JDK 1.5 标准库的 concurrent 库,根据多线程的使用情况进行锁定优化。这方面 C/C++ 库的设计理念非常优秀,那就是决不为用不到的东西付出代价。
对后者来说,则可以发挥 JVM 与传统静态编译相比最大的优势,根据运行时行为进行优化。

Lock elision 的思路,就是通过 escape 分析找出根本不存在多线程引用可能性的锁。对这种情况 Java 语言规范中明确允许进行优化,直接去掉不必要的锁定语义。而最大的问题则在于如何进行 escape 分析,并找到锁的可优化范围。因为相对于基于栈的 C/C++ 来说,Java 内存模型目前是完全基于堆的,每个对象都在全局堆中可以由任何外部线程访问。
有兴趣深入研究的话,可以看看下面这两篇文章

[url]http://www.research.ibm.com/people/g/gupta/toplas03.pdf[/url]

[url]http://www.research.ibm.com/people/j/jdchoi/escape-pointsto.html[/url]

Adaptive locking 的思路,则是将 contended 的锁定按锁定时间进一步细分。因为对大多数锁定来说,锁定的时间都是非常短的,例如很多 get/set 方法,以及简单的内存操作。对这些方法来说,如果要完成一个完整的锁定流程,需要涉及到对象锁状态更新、等待线程对象构建、甚至与操作系统一级的线程调度打交道。相对于实际要工作所耗费的时间来说,锁定这个操作自身消耗的资源可能反而是大头。对这些情况而言,与其建立一个完整的锁上下文,不如直接用 spin 锁机制进行等待:



































以下内容为程序代码:

  while (lockStillInUse)
      ;




如果一定时间或尝试次数后还是无法获得锁,则 JVM 可以将此锁的类型转换为长时间的锁,然后构造完整的锁上下文进行管理。而这种优化方法,最能够体现动态 JIT 相对于传统静态编译器的优势,因为这些执行行为的信息是无法静态获取的。

Lock coarsening 的思路,则是通过将临近的几个相同锁定进行合并,以减少不必要的重复锁定操作。这种优化的原理是基于重复方法往往同时出现的模式,例如下面这种常见情况

































以下内容为程序代码:
CODE:
public void addStooges(Vector v) { v.add(\"Moe\"[img]/images/wink.gif[/img]; v.add(\"Larry\"[img]/images/wink.gif[/img]; v.add(\"Curly\"[img]/images/wink.gif[/img]; }
[Copy to clipboard]
对这种情况来说,每次调用 add 方法进行锁定和解锁是没必要的,JVM 可以根据运行时信息选择性合并同类型锁。随着现在机器自动代码生成的广泛引用,可以预期这种基于行为对锁进行合并的思路会非常有用。

此外 David Dagastine 在其 blog 上也对 Java 同步锁优化进行了讨论

关于 Escape 分析 Brian Goetz 还在另外一篇讨论 Java 性能问题误解的文章中有所提及。

Urban performance legends
Urban performance legends, revisited

文中讨论了一些对 Java 性能问题的常见误解,其中很多问题是旧版本 JVM 和 Java 库中存在的,随着 JDK 的不断更新已经不同程度上得到缓解,例如下述等等问题。

Synchronization is really slow
Declaring classes or methods final makes them faster
Immutable objects are bad for performance

这些问题的出现,往往是因为使用者对 JVM 的实现和优化思路不熟悉导致的。实际上 HotSpot 自从 JDK 1.3 版本以后,实际上有了非常大的进步,无论是从功能还是性能上,都已经远远超出了某些人的预期。而在可以预见的 Mustang 和 Dolphin 中,更高级和动态的优化还会不断加入进来,并从 JVM 一级对应用产生透明的性能提升。

例如基于 Escape 分析的栈分配优化,有可能一改 JVM 只从堆中建立对象的传统,直接将动态分析得到的仅由线程自身使用的对象,以类似 C/C++ 的方式直接在栈中分配。这一改进如果能够成熟,对 JVM 性能的提升将是非常可观的。因为对现代 CPU 架构来说,提升性能的一个关键问题就在于 Cache 的使用。相对于位置离散的堆来说,栈一般都能确保在缓存中的命中率;而在堆中分配内存,哪怕分配性能很高,但因为缓存往往无法命中,带来的性能缺失会非常大。而且使用基于栈的内存管理模式,还可以享受到析构对象的快捷性,函数调用完成后,只需要将原函数的栈顶指针弹出即可释放所有临时对象。

如果对此方面有兴趣,可以看看 Brian Goetz 这个非常不错的 Java theory and practice 专栏 ,下面是其中关于性能优化的其他文章

Performance management -- do you have a plan?
Is that your final answer?
To mutate or not to mutate?



  真正的思考所依据的不只是经验指数,他甚至在推翻经验、创造新的机会。所以思考是有趣的,他不一定是逻辑;更不是主观的判断。

javajtg520 发表于 2007-4-27 18:37

好辛苦 拉 斑竹

同样支持    java会在   这里发扬光大的

页: [1]

Powered by Discuz! Archiver 7.0.0  © 2001-2009 Comsenz Inc.