java8中CAS的增强

注: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 [email protected]
  */
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

时间: 2024-11-05 18:47:06

java8中CAS的增强的相关文章

Java 8 中 CAS 的增强

几天前,我偶然地将之前写的用来测试AtomicInteger和synchronized的自增性能的代码跑了一下,意外地发现AtomicInteger的性能比synchronized更好了,经过一番原因查找,有了如下发现: 在jdk1.7中,AtomicInteger的getAndIncrement是这样的: public final int getAndIncrement() { for (;;) { int current = get(); int next = current + 1; if

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

简介: 本文主要介绍Java8中的并发容器ConcurrentHashMap的工作原理,和其它文章不同的是,本文重点分析了不同线程的各类并发操作如get,put,remove之间是如何同步的,以及这些操作和扩容操作之间同步可能出现的各种情况.由于源代码的分析肯定会有所纰漏,希望大家积极指出错误. 欢迎探讨,如有错误敬请指正 如需转载,请注明出处 http://www.cnblogs.com/nullzx/ 1.Java8中 ConcurrentHashMap的结构 图片来源(http://www

Java8中新增的日期和时间API

1.计算时间间隔 Java8之前,我们想要确定一个方法的运行时间长度,可以这样: long start = System.currentTimeMillis(); doSomething(); long end = System.currentTimeMillis(); System.out.println(end-start); Java8中,可以这样 Instant start = Instant.now(); doSomething(); Instant end = Instant.now

Java8中聚合操作collect、reduce方法详解

Stream的基本概念 Stream和集合的区别: Stream不会自己存储元素.元素储存在底层集合或者根据需要产生.Stream操作符不会改变源对象.相反,它会返回一个持有结果的新的Stream.3.Stream操作可能是延迟执行的,这意味着它们会等到需要结果的时候才执行.Stream操作的基本过程,可以归结为3个部分: 创建一个Stream.在一个或者多个操作中,将指定的Stream转换为另一个Stream的中间操作.通过终止(terminal)方法来产生一个结果.该操作会强制它之前的延时操

java8 中的时间和数据的变化

java8除了lambda表达式之外还对时间和数组这两块常用API做想应调整, Stream 有几个常用函数: store 排序 (a,b)-> a.compareTo(b)  排出来的结果是正序,从小到大 如果(a,b)->b.compareTo(a) 排出来的结果是从大到小 filter 拦截,过滤.根据条件过滤 forEeach 迭代每个元素 map 变更或者替换 limit 长度或者符合数量的限制 skip 跳到某个开始点,skip和limit组合可以制造分页的效果 sum ,coun

Java8 中的Optional类详解

在Java8中,引入了一个新的类java.util.Optional .这个类专门用来解决空引用的问题.有了它,对于一个方法的返回值什么的,都不需要再判断是不是null了.同时,这个类和lambda表达式和函数式编程也可以比较好的整合在一起使用. 这里就来看一下这个类的使用. 知道Scala的同学可以类比一下 scala.Option[T] ,或者Haskell的Data.Maybe.都有类似的功能. Optional是一个容器,里面放着我们的返回值(就是真正要用的东西),但是这个真正要用的东西

BigInteger在Java8中的改进

BigInteger在Java8里增加了一组方法: 123 public byte byteValueExact()public int intValueExact()public long longValueExact() 这些方法后面都有Exact(),在老的JDK版本中,已经有了byteValue,intValue,longValue()为什么还要再增加这些方法呢?因为在原来的方法中,如果BigInteger的值溢出了要目标类型的范围,是不会有任何提示的,那么我们的程序很可能在一个很隐蔽的

java8中的接口

java8中接口可以有默认方法(用default修饰,可以有多个)和静态方法了. public interface Tran { default public String getName() { return "zhangsan"; } default public String getName1() { return "lisi"; } public static String getName2() throws Exception{ return "

ConcurrentHashMap在Java8中的变化

增加红黑树这个存储结构 在Java8中,为什么要增加红黑树这种数据结构来进行存储,而不是全部使用链表来进行存储呢? 1.因为攻击者可以构造大量具有相同hashCode的内容,使其全部放在同一个列表中,这样,在查找的时候,所花费的时间会很长.这个时候,如果采用红黑树这个结构来进行存储,那么其查找的效率会高很多. 2.hashCode()函数的计算有时候并不合理,例如重写hashCode函数的时候.如果都映射到同一个位置,那么查找的时间也会很长. mappingCount() 在Concurrent