zoukankan      html  css  js  c++  java
  • 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 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

  • 相关阅读:
    iOS下JS与OC互相调用(三)--MessageHandler
    在xcode6中使用矢量图(iPhone6置配UI)
    UITextField增加textDidChange回调功能
    IOS开发之格式化日期时间(转)
    ios中将事件同步到系统日历
    xcode中一些便捷用法@literals简写
    JavaScript
    fuzz for test of the Net::HTTP::GET
    perl6 中将 字符串 转成十六进制
    Net::HTTP 一次添加 cookie, body 发送post请求
  • 原文地址:https://www.cnblogs.com/trytocatch/p/enhanced-cas-in-jdk8.html
Copyright © 2011-2022 走看看