Java 8 中 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中,没有此方法。(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的特殊处理也就是我上文所说的“黑魔法”。

相关链接:

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-12-12 06:42:51

Java 8 中 CAS 的增强的相关文章

java8中CAS的增强

注:ifeve.com的同名文章为本人所发,此文在其基础做了些调整.转载请注明出处! 一.java8中CAS的增强 前些天,我偶然地将之前写的用来测试AtomicInteger和synchronized的自增性能的代码跑了一下,意外地发现AtomicInteger的性能比synchronized更好了,经过一番原因查找,有了如下发现: 在jdk1.7中,AtomicInteger的getAndIncrement是这样的: public final int getAndIncrement() {

【转】Java并发中正确使用volatile

Java并发中正确使用volatile 原文链接 http://ifeve.com/how-to-use-volatile/ 作者:一粟   整理和翻译自Twitter实时搜索的PPT 前几天并发编程群里有同学对volatile的用法提出了疑问,刚好我记得Twitter有关实时搜索的这个PPT对这个问题解释的很清晰并有一个实际的应用场景,于是周末把这个问题摘录了一些和并发相关的内容如下: 并发 – 定义 悲观锁 – Pressimistic locking 一个线性在执行一个操作时持有对一个资源

JAVA面试中问及HIBERNATE与 MYBATIS的对比,在这里做一下总结(转)

hibernate以及mybatis都有过学习,在java面试中也被提及问道过,在项目实践中也应用过,现在对hibernate和mybatis做一下对比,便于大家更好的理解和学习,使自己在做项目中更加得心应手. 第一方面:开发速度的对比 就开发速度而言,Hibernate的真正掌握要比Mybatis来得难些.Mybatis框架相对简单很容易上手,但也相对简陋些.个人觉得要用好Mybatis还是首先要先理解好Hibernate. 比起两者的开发速度,不仅仅要考虑到两者的特性及性能,更要根据项目需求

java项目中可能会使用到的jar包解释

一.Struts2 用的版本是struts2.3.1.1 一个简单的Struts项目所需的jar包有如下8个 1. struts2-core-2.3.1.1.jar: Struts2的核心类库. 2. xwork-core-2.3.1.1.jar: XWork核心类,XWork是一个标准的command模式实现,并且完全从web层剥离出来.WebWork被构建在Xwork上,而Struts2由Struts1和WebWork两个经典的MVC框架发展而来. 3. ognl-3.0.3.jar: 支持

Java EE中常用的四个框架

Java EE中常用的四个框架     Struts     Struts是一个基于Sun Java EE平台的MVC框架,主要是采用Servlet和JSP技术来实现的.     Struts框架可分为以下四个主要部分,其中三个就和MVC模式紧密相关:     1.模型 (Model),本质上来说在Struts中Model是一个Action类(这个会在后面详细讨论),开发者通过其实现商业逻辑,同时用户请求通过控制器(Controller)向Action的转发过程是基于由struts-config

Java 7中的TransferQueue 以及 SynchronousQueue

Java7中加入了JSR 166y规范对集合类和并发类库的改进.其中的一项是增加了接口TransferQueue和其实现类LinkedTransferQueue. TransferQueue继承了BlockingQueue(BlockingQueue又继承了Queue)并扩展了一些新方法.BlockingQueue(和Queue)是Java 5中加入的接口,它是指这样的一个队列:当生产者向队列添加元素但队列已满时,生产者会被阻塞:当消费者从队列移除元素但队列为空时,消费者会被阻塞. Transf

Java 8 中的 Streams API 详解

为什么需要 Stream Stream 作为 Java 8 的一大亮点,它与 java.io 包里的 InputStream 和 OutputStream 是完全不同的概念.它也不同于 StAX 对 XML 解析的 Stream,也不是 Amazon Kinesis 对大数据实时处理的 Stream.Java 8 中的 Stream 是对集合(Collection)对象功能的增强,它专注于对集合对象进行各种非常便利.高效的聚合操作(aggregate operation),或者大批量数据操作 (

JAVA基础篇七(Java,C++中的线程)

讲到线程,Java的线程目前只知道从thread继承,并用start函数启动线程,稍后会多了解JAVA线程相关知识,补充到这一章中. C++的线程在读硕期间用到过多次,下面首先总结一下: 1.C++线程 (1)MFC多线程 这种方式我没用过,所以这里只是提一下,主要在MFC中,一般用全局函数AfxBeginThread()来创建并初始化一个线程的运行,该函数有两种重载形式,分别用于创建工作者线程和用户界面线程. (2)phtread POSIX线程(POSIX threads),简称Pthrea

java 8 中的stream

Java 8 中的 Streams 详解 Java 8 是迄今为止在语义上改动上最大的一个平台.除了最显著的 Lambda 表达式之外,还有很多初次见面的特性,例如本文主题的 Streams API.这里介绍了它出现的背景和具体用法. 为什么需要 Stream Stream 作为 Java 8 的一大亮点,它与 java.io 包里的 InputStream 和 OutputStream 是完全不同的概念.它也不同于 StAX 对 XML 解析的 Stream,也不是 Amazon Kinesi