注:ifeve.com的同名文章为本人所发,此文在其基础做了些调整。转载请注明出处!


一、java8中CAS的增强

前些天,我偶然地将之前写的用来测试AtomicInteger和synchronized的自增性能的代码跑了一下,意外地发现AtomicInteger的性能比synchronized更好了,经过一番原因查找,有了如下发现:

在jdk1.7中,AtomicInteger的getAndIncrement是这样的:

public final int getAndIncrement() {
for (;;) {
int current = get();
int next = current + 1;
if (compareAndSet(current, next))
return current;
}
}
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}

而在jdk1.8中,是这样的:

public final int getAndIncrement() {
return unsafe.getAndAddInt(this, valueOffset, 1);
}

可以看出,在jdk1.8中,直接使用了Unsafe的getAndAddInt方法,而在jdk1.7的Unsafe中,没有此方法。基本可以断定,Unsafe新增的方法是性能提升的关键。(文章末尾将附上一些探索的过程及推论)

通过查看AtomicInteger的源码可以发现,受影响的还有getAndAdd、addAndGet等大部分方法。

结论:有了这次对CAS的增强,我们又多了一个使用非阻塞算法的理由。


二、测试方法

以下给出测试代码,供参考与测试。需要注意的是,此测试方法简单粗暴,compareAndSet的性能不如synchronized,并不能简单地说synchronized就更好,两者的使用方式是存在差异的,而且在实际使用中,还有业务处理,不可能有如此高的竞争强度,此对比仅作为一个参考,该测试能够证明的是,AtomicInteger.getAndIncrement的性能有了大幅提升。

package performance;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.LockSupport; /**
* @author trytocatch@163.com
*/
public class AtomicTest {
//测试规模,调用一次getAndIncreaseX视作提供一次业务服务,记录提供TEST_SIZE次服务的耗时
private static final int TEST_SIZE = 100000000;
//客户线程数
private static final int THREAD_COUNT = 10;
//使用CountDownLatch让各线程同时开始
private CountDownLatch cdl = new CountDownLatch(THREAD_COUNT + 1); private int n = 0;
private AtomicInteger ai = new AtomicInteger(0);
private long startTime; public void init() {
startTime = System.nanoTime();
} /**
* 使用AtomicInteger.getAndIncrement,测试结果为1.8比1.7有明显性能提升
* @return
*/
private final int getAndIncreaseA() {
int result = ai.getAndIncrement();
if (result == TEST_SIZE) {
System.out.println(System.nanoTime() - startTime);
System.exit(0);
}
return result;
} /**
* 使用synchronized来完成同步,测试结果为1.7和1.8几乎无性能差别
* @return
*/
private final int getAndIncreaseB() {
int result;
synchronized (this) {
result = n++;
}
if (result == TEST_SIZE) {
System.out.println(System.nanoTime() - startTime);
System.exit(0);
}
return result;
} /**
* 使用AtomicInteger.compareAndSet在java代码层面做失败重试(与1.7的AtomicInteger.getAndIncrement的实现类似),
* 测试结果为1.7和1.8几乎无性能差别
* @return
*/
private final int getAndIncreaseC() {
int result;
do {
result = ai.get();
} while (!ai.compareAndSet(result, result + 1));
if (result == TEST_SIZE) {
System.out.println(System.nanoTime() - startTime);
System.exit(0);
}
return result;
} public class MyTask implements Runnable {
@Override
public void run() {
cdl.countDown();
try {
cdl.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
while (true)
getAndIncreaseA();// getAndIncreaseB();
}
} public static void main(String[] args) throws InterruptedException {
AtomicTest at = new AtomicTest();
for (int n = 0; n < THREAD_COUNT; n++)
new Thread(at.new MyTask()).start();
System.out.println("start");
at.init();
at.cdl.countDown();
}
}

以下是在Intel(R) Core(TM) i7-4710HQ CPU @2.50GHz(四核八线程)下的测试结果(波动较小,所以每项只测试了四五次,取其中一个较中间的值):

jdk1.7
AtomicInteger.getAndIncrement 12,653,757,034
synchronized 4,146,813,462
AtomicInteger.compareAndSet 12,952,821,234 jdk1.8
AtomicInteger.getAndIncrement 2,159,486,620
synchronized 4,067,309,911
AtomicInteger.compareAndSet 12,893,188,541 

三、提升原因的探索及推论

一开始,我怀疑在1.8中,Unsafe直接使用了native方法,而1.7是在getAndIncrement里完成的失败重试,也就是在java代码层面,所以造成了性能的差别,于是我用jad反编译了Unsafe,得到了如下代码:

public final int getAndAddInt(Object obj, long l, int i)
{
int j;
do
j = getIntVolatile(obj, l);
while(!compareAndSwapInt(obj, l, j, j + i));
return j;
}
public native int getIntVolatile(Object obj, long l);
public final native boolean compareAndSwapInt(Object obj, long l, int i, int j);

并且参考了openjdk8的Unsafe源码:

public final int getAndAddInt(Object o, long offset, int delta) {
int v;
do {
v = getIntVolatile(o, offset);
} while (!compareAndSwapInt(o, offset, v, v + delta));
return v;
}
public native int getIntVolatile(Object o, long offset);
public final native boolean compareAndSwapInt(Object o, long offset,
int expected,
int x);

由上面的信息可以看出,1.8中,失败重试也是在java代码层面进行的(区别是转移到了Unsafe的java方法里面),算是推翻了我的猜测,于是我决定通过反射,直接获取到Unsafe实例,编写跟Unsafe.getAndAddInt方法一样的代码来测试,看能否找到一些新的线索:

...
import sun.misc.Unsafe;
public class AtomicTest {
....
private Unsafe unsafe;
private long valueOffset;
public AtomicTest(){
Field f;
try {
f = Unsafe.class.getDeclaredField("theUnsafe");
f.setAccessible(true);
unsafe = (Unsafe)f.get(null);
valueOffset = unsafe.objectFieldOffset(AtomicInteger.class.getDeclaredField("value"));
}catch(NoSuchFieldException e){
...
}
}
private final int getAndIncreaseD(){
int result;
do{
result = unsafe.getIntVolatile(ai, valueOffset);
}while(!unsafe.compareAndSwapInt(ai, valueOffset, result, result+1));
if(result == MAX){
System.out.println(System.nanoTime()-startTime);
System.exit(0);
}
return result;
}
...
}

但让人失望的是,该方式跟1.7的getAndIncrement效率一样,明明跟1.8的Unsafe.getAndAddInt方法一样,却是截然不同的效率。

最后,经过ifeve.com的网友们的指点,对性能的提升原因有了如下推论,虽不敢说百分之百正确(因为没有用jvm的源码作为论据),但还是有很大把握的,感谢网友@周 可人和@liuxinglanyue!

Unsafe是经过特殊处理的,不能理解成常规的java代码,区别在于:

  • 1.8在调用getAndAddInt的时候,如果系统底层支持fetch-and-add,那么它执行的就是native方法,使用的是fetch-and-add;
  • 如果不支持,就按照上面的所看到的getAndAddInt方法体那样,以java代码的方式去执行,使用的是compare-and-swap;

这也正好跟openjdk8中Unsafe::getAndAddInt上方的注释相吻合:

  • // The following contain CAS-based Java implementations used on
    // platforms not supporting native instructions

相关链接:
http://ashkrit.blogspot.com/2014/02/atomicinteger-java-7-vs-java-8.html
http://hg.openjdk.java.net/jdk8u/hs-dev/jdk/file/a006fa0a9e8f/src/share/classes/sun/misc/Unsafe.java

java8中CAS的增强的更多相关文章

  1. Java 8 中 CAS 的增强

    几天前,我偶然地将之前写的用来测试AtomicInteger和synchronized的自增性能的代码跑了一下,意外地发现AtomicInteger的性能比synchronized更好了,经过一番原因 ...

  2. 谈一谈Java8的函数式编程(二) --Java8中的流

    流与集合    众所周知,日常开发与操作中涉及到集合的操作相当频繁,而java中对于集合的操作又是相当麻烦.这里你可能就有疑问了,我感觉平常开发的时候操作集合时不麻烦呀?那下面我们从一个例子说起. 计 ...

  3. Java8 中 ConcurrentHashMap工作原理的要点分析

    简介: 本文主要介绍Java8中的并发容器ConcurrentHashMap的工作原理,和其它文章不同的是,本文重点分析了不同线程的各类并发操作如get,put,remove之间是如何同步的,以及这些 ...

  4. Java7 和 Java8 中的 ConcurrentHashMap 原理解析

    Java7 中 ConcurrentHashMap ConcurrentHashMap 和 HashMap 思路是差不多的,但是因为它支持并发操作,所以要复杂一些. 整个 ConcurrentHash ...

  5. java7,java8 中HashMap和ConcurrentHashMap简介

    一:Java7 中的HashMap 结构: HashMap 里面是一个数组,然后数组中每个元素是一个单向链表.链表中每个元素称为一个Entry 实例,Entry 包含四个属性:key, value, ...

  6. Java8函数之旅 (二) --Java8中的流

    流与集合    众所周知,日常开发与操作中涉及到集合的操作相当频繁,而java中对于集合的操作又是相当麻烦.这里你可能就有疑问了,我感觉平常开发的时候操作集合时不麻烦呀?那下面我们从一个例子说起. 计 ...

  7. Java7与Java8中的HashMap和ConcurrentHashMap知识点总结

    JAVA7 Java7的ConcurrentHashMap里有多把锁,每一把锁用于其中一部分数据,那么当多线程访问容器里不同数据段的数据时,线程间就不会存在锁竞争,从而可以有效的提高并发访问效率呢.这 ...

  8. Java8中的Stream API

    本篇文章继续介绍Java 8的另一个新特性——Stream API.新增的Stream API与InputStream和OutputStream是完全不同的概念,Stream API是对Java中集合 ...

  9. java8中map的meger方法的使用

    java8中map有一个merge方法使用示例: /** * 打印出包含号码集的label的集合 * * @param args */ public static void main(String[] ...

随机推荐

  1. C#遍历集合与移除元素的方法

    如果用foreach,会造成被遍历的集合更改后带来异常问题. 此时,用for循环可有效的解决这个问题. for(int i=0;i<List.Count;i++) { if(条件是真) { Li ...

  2. nginx 配置全站404(百度闭站保护)

    在百度站长里申请闭站保护时,需要全站404.可能过nginx配置实现 location / {   #root html;   #index index.html index.htm;   retur ...

  3. EasyUI TextBox的onkeypress事件

    关于EasyUI TextBox的事件好像不多,像keypress,keydown在textbox的事件里都没有,所以要用这些事件要采取一些特殊的方法,今天用到了这些就记录一下,有两种方法 方法1: ...

  4. XML文件中CDATA的作用

    操作XML文件时,如果允许用户输入内容,例如∶"< ".">"."/".""等,当生成XML时,会破坏了XM ...

  5. HTTP权威协议笔记-7.集成点:网关、隧道及中继

    .8.1 网关 定义:网关类似与翻译器,它抽象出了一种能够到达资源的方法. 实用:网关可以自动将HTTP流量转换为其他协议,这样使用HTTP协议的一方就不需要了解其他协议,也可实现与其他程序或设备交互 ...

  6. Backbone框架浅析

    Backbone是前端mvc开发模式的框架.它能够让view和model相分离,让代码结构更清晰简答,开发进度加快,维护代码方便.但是,现在出了一种mvvm框架,它是下一代前端mvc开发模式的框架,代 ...

  7. 优化 从Draw Calls到GC

    原文出处: 慕容小匹夫的博客(@慕容小匹夫)   欢迎分享原创到伯乐头条 前言: 刚开始写这篇文章的时候选了一个很土的题目...<Unity3D优化全解析>.因为这是一篇临时起意才写的文章 ...

  8. [原创]Lodop打印, 以及Lodop引用css文件控制打印样式的问题.

    最近在做Lodop打印功能: 思路是:  用MasterPage搭个打印页面的框架, 然后在具体的页面中填入数据, 打印的样式由母版页和CSS来控制. 困扰了一天的问题是:  在打印的JS文件中, 引 ...

  9. MS SQLSERVER 存儲過程與緩存

    提升SQL Server最具性能的一个方面就是存储过程,SQL Server具备执行计划的缓存功能,以便计划重用.SQL Server2000增强了ad-hoc执行计划的缓存功能,就处理存储过程上性能 ...

  10. a标签中href的触发

    采用.trigger('click')没有效果,是用的$('xx')[0].click()来触发的.不知道为什么trigger不行,望指导.