Java6中线程优化及基准测试思路 (一)
转载的文章, 文章里的思路太NB了。
原文: http://www.infoq.com/cn/articles/java-threading-optimizations-p1
介绍 — Java 6中的线程优化
Sun、IBM、BEA和其他公司在各自实现的Java 6虚拟机上都花费了大量的精力优化锁的管理和同步。诸如偏向锁(biased locking)、锁粗化(lock coarsening)、由逸出(escape)分析产生的锁省略、自适应自旋锁(adaptive spinning)这些特性,都是通过在应用程序线程之间更高效地共享数据,从而提高并发效率。尽管这些特性都是成熟且有趣的,但是问题在于:它们的承诺真的能实现么?在这篇由两部分组成的文章里,我将逐一探究这些特性,并尝试在单一线程基准的协助下,回答关于性能的问题。
悲观锁模型
Java支持的锁模型绝对是悲观锁(其实,大多数线程库都是如此)。如果有两个或者更多线程使用数据时会彼此干扰,这种极小的风险也会强迫我们采用非常严厉的手段防止这种情况的发生——使用锁。然而研究表明,锁很少被占用。也就是说,一个访问锁的线程很少必须等待来获取它。但是请求锁的动作将会触发一系列的动作,这可能导致严重的系统开销,这是不可避免的。
publicString concatBuffer(String s1, String s2, String s3) {, StringBuffer sb = new StringBuffer(); sb.append(s1); sb.append(s2); sb.append(s3); return sb.toString(); }
如果我们观察变量sb,很快就会发现它仅仅被限制在concatBuffer方法内部了。进一步说,到sb的所有引用永远不会“逸出”到 concatBuffer方法之外,即声明它的那个方法。因此其他线程无法访问当前线程的sb副本。根据我们刚介绍的知识,我们知道用于保护sb的锁可以忽略掉。
从表面上看,锁省略似乎可以允许我们不必忍受同步带来的负担,就可以编写线程安全的代码了,前提是在同步的确是多余的情况下。锁省略是否真的能发挥作用呢?这是我们在后面的基准测试中将要回答的问题。
简析偏向锁(Biased locking explained)
大多数锁,在它们的生命周期中,从来不会被多于一个线程所访问。即使在极少数情况下,多个线程真的共享数据了,锁也不会发生竞争。为了理解偏向锁的优势,我们首先需要回顾一下如何获取锁(监视器)。
获取锁的过程分为两部分。首先,你需要获得一份契约.一旦你获得了这份契约,就可以自由地拿到锁了。为了获得这份契约,线程必须执行一个代价昂贵的原子指令。释放锁同时就要释放契约。根据我们的观察,我们似乎需要对一些锁的访问进行优化,比如线程执行的同步块代码在一个循环体中。优化的方法之一就是将锁粗化,以包含整个循环。这样,线程只访问一次锁,而不必每次进入循环时都进行访问了。但是,这并非一个很好的解决方案,因为它可能会妨碍其他线程合法的访问。还有一个更合理的方案,即将锁偏向给执行循环的线程。
将锁偏向于一个线程,意味着该线程不需要释放锁的契约。因此,随后获取锁的时候可以不那么昂贵。如果另一个线程在尝试获取锁,那么循环线程只需要释放契约就可以了。Java 6的HotSpot/JIT默认情况下实现了偏向锁的优化。
简析锁粗化(Lock coarsening explained)
另一种线程优化方式是锁粗化(或合并,merging)。当多个彼此靠近的同步块可以合并到一起,形成一个同步块的时候,就会进行锁粗化。该方法还有一种变体,可以把多个同步方法合并为一个方法。如果所有方法都用一个锁对象,就可以尝试这种方法。考虑图2中的实例。
public static String concatToBuffer(StringBuffer sb, String s1, String s2, String s3) { sb.append(s1); sb.append(s2); sb.append(s3); return }
public class LockTest { private static final int MAX = 20000000; // 20 million public static void main(String[] args) throws InterruptedException { // warm up the method cache for (int i = 0; i < MAX; i++) { concatBuffer("Josh", "James", "Duke"); concatBuilder("Josh", "James", "Duke"); } System.gc(); Thread.sleep(1000); System.out.println("Starting test"); long start = System.currentTimeMillis(); for (int i = 0; i < MAX; i++) { concatBuffer("Josh", "James", "Duke"); } long bufferCost = System.currentTimeMillis() - start; System.out.println("StringBuffer: " + bufferCost + " ms."); System.gc(); Thread.sleep(1000); start = System.currentTimeMillis(); for (int i = 0; i < MAX; i++) { concatBuilder("Josh", "James", "Duke"); } long builderCost = System.currentTimeMillis() - start; System.out.println("StringBuilder: " + builderCost + " ms."); System.out.println("Thread safety overhead of StringBuffer: " + ((bufferCost * 10000 / (builderCost * 100)) - 100) + "%\n"); } public static String concatBuffer(String s1, String s2, String s3) { StringBuffer sb = new StringBuffer(); sb.append(s1); sb.append(s2); sb.append(s3); return sb.toString(); } public static String concatBuilder(String s1, String s2, String s3) { StringBuilder sb = new StringBuilder(); sb.append(s1); sb.append(s2); sb.append(s3); return sb.toString(); } }java-server -XX:+DoEscapeAnalysis LockTest