博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Java8中CAS的增强
阅读量:7045 次
发布时间:2019-06-28

本文共 5561 字,大约阅读时间需要 18 分钟。

几天前,我偶然地将之前写的用来测试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的源码:

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的特殊处理也就是我上文所说的“黑魔法”。

转载地址:http://ipeal.baihongyu.com/

你可能感兴趣的文章
Lambda 表达式
查看>>
Linux Shell常用技巧(二) grep
查看>>
tomcat class nofound,spring mvc的get请求乱码处理
查看>>
MSSQL手工注入
查看>>
修改开机提示
查看>>
RAID的使用详解
查看>>
redis 集群架构 cluster 、sentinel
查看>>
将 jsonString 转化成成java对象
查看>>
tomcat配置与应用(1)
查看>>
51cto PMP认证微职位4期强化班心得
查看>>
Go中常用包笔记 内置builtin(一)
查看>>
js-->贪吃蛇小游戏,能成功玩
查看>>
二维数组和它的指针
查看>>
腾讯股票接口、和讯网股票接口、新浪股票接口、雪球股票数据、网易股票数据...
查看>>
Redis 数据类型详解
查看>>
Java消息队列——ActiveMQ笔记(开发前准备工作)
查看>>
阿里云网站域名备案流程全过程(图文讲解)
查看>>
【运维安全】-***流程
查看>>
看表空间大小,查询表空间名的表空间文件存放位置,矿展表空间,查看表空间名称...
查看>>
window下查看端口占用情况
查看>>