• 技术文章 >Java >java教程

    具体介绍Java8中CAS的增强

    黄舟黄舟2017-03-24 10:48:40原创941
    几天前,我偶然地将之前写的用来测试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中,没有此方法。(PS:为了找出原因,我反编译了Unsafe,发现CAS的失败重试就是在getAndAddInt方法里完成的,我用反射获取到Unsafe实例,编写了跟getAndAddInt相同的代码,但测试结果却跟jdk1.7的getAndIncrement一样慢,不知道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;
    
    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

    补充:应网友要求,在此提供Unsafe.getAndAddInt的相关源码以及我的测试代码。

    用jad反编译jdk1.8中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);

    我的测试代码(提示:如果eclipse等ide报错,那是因为使用了受限的Unsafe,可以将警告级别从error降为warning,具体百度即可):

    ...
    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;
    	}
    	...
    }

    补充2:对于性能提升的原因,有以下推论,虽不敢说百分之百正确(因为没有用jvm的源码作为论据),但还是有很大把握的,感谢网友@周 可人和@liuxinglanyue!

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

    在调用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

    Unsafe的特殊处理也就是我上文所说的“黑魔法”。

    以上就是具体介绍Java8中CAS的增强的详细内容,更多请关注php中文网其它相关文章!

    声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn核实处理。
    专题推荐:Java8,CAS
    上一篇:Getty-实现Java NIO框架设计的详解 下一篇:Java Spring中不为人知的注入方式详解
    Web大前端开发直播班

    相关文章推荐

    • Java技巧总结之如何看Lambda源码• 完全掌握Java单例模式• JAVA详细解析之IO流、File、字节流以及字符流• JAVA学习IO操作之字节流和字符流(总结分享)• JAVA面向对象之继承、构造方法、重写和重载(总结分享)
    新人一分购

    全部评论我要评论

  • 取消发布评论发送
  • 1/1

    PHP中文网