Java8中CAS的增强

简介:

几天前,我偶然地将之前写的用来测试AtomicInteger和synchronized的自增性能的代码跑了一下,意外地发现AtomicInteger的性能比synchronized更好了,经过一番原因查找,有了如下发现:

在jdk1.7中,AtomicInteger的getAndIncrement是这样的:

01 public final int getAndIncrement() {
02     for (;;) {
03         int current = get();
04         int next = current + 1;
05         if (compareAndSet(current, next))
06             return current;
07     }
08 }
09 public final boolean compareAndSet(int expect, int update) {
10     return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
11 }

而在jdk1.8中,是这样的:

1 public final int getAndIncrement() {
2     return unsafe.getAndAddInt(this, valueOffset, 1);
3 }

可以看出,在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的性能有了大幅提升。

01 package performance;
02  
03 import java.util.concurrent.CountDownLatch;
04 import java.util.concurrent.atomic.AtomicInteger;
05 import java.util.concurrent.locks.LockSupport;
06  
07 public class AtomicTest {
08     //测试规模,调用一次getAndIncreaseX视作提供一次业务服务,记录提供TEST_SIZE次服务的耗时
09     private static final int TEST_SIZE = 100000000;
10     //客户线程数
11     private static final int THREAD_COUNT = 10;
12     //使用CountDownLatch让各线程同时开始
13     private CountDownLatch cdl = new CountDownLatch(THREAD_COUNT + 1);
14  
15     private int n = 0;
16     private AtomicInteger ai = new AtomicInteger(0);
17     private long startTime;
18  
19     public void init() {
20         startTime = System.nanoTime();
21     }
22  
23     /**
24      * 使用AtomicInteger.getAndIncrement,测试结果为1.8比1.7有明显性能提升
25      * @return
26      */
27     private final int getAndIncreaseA() {
28         int result = ai.getAndIncrement();
29         if (result == TEST_SIZE) {
30             System.out.println(System.nanoTime() - startTime);
31             System.exit(0);
32         }
33         return result;
34     }
35  
36     /**
37      * 使用synchronized来完成同步,测试结果为1.7和1.8几乎无性能差别
38      * @return
39      */
40     private final int getAndIncreaseB() {
41         int result;
42         synchronized (this) {
43             result = n++;
44         }
45         if (result == TEST_SIZE) {
46             System.out.println(System.nanoTime() - startTime);
47             System.exit(0);
48         }
49         return result;
50     }
51  
52     /**
53      * 使用AtomicInteger.compareAndSet在java代码层面做失败重试(与1.7的AtomicInteger.getAndIncrement的实现类似),
54      * 测试结果为1.7和1.8几乎无性能差别
55      * @return
56      */
57     private final int getAndIncreaseC() {
58         int result;
59         do {
60             result = ai.get();
61         while (!ai.compareAndSet(result, result + 1));
62         if (result == TEST_SIZE) {
63             System.out.println(System.nanoTime() - startTime);
64             System.exit(0);
65         }
66         return result;
67     }
68  
69     public class MyTask implements Runnable {
70         @Override
71         public void run() {
72             cdl.countDown();
73             try {
74                 cdl.await();
75             catch (InterruptedException e) {
76                 e.printStackTrace();
77             }
78             while (true)
79                 getAndIncreaseA();// getAndIncreaseB();
80         }
81     }
82  
83     public static void main(String[] args) throws InterruptedException {
84         AtomicTest at = new AtomicTest();
85         for (int n = 0; n < THREAD_COUNT; n++)
86             new Thread(at.new MyTask()).start();
87         System.out.println("start");
88         at.init();
89         at.cdl.countDown();
90     }
91 }

以下是在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得到的源码:

01 public final int getAndAddInt(Object obj, long l, int i)
02 {
03     int j;
04     do
05         j = getIntVolatile(obj, l);
06     while(!compareAndSwapInt(obj, l, j, j + i));
07     return j;
08 }
09 public native int getIntVolatile(Object obj, long l);
10 public final native boolean compareAndSwapInt(Object obj, long l, int i, int j);

openjdk8的Unsafe源码:

01 public final int getAndAddInt(Object o, long offset, int delta) {
02     int v;
03     do {
04         v = getIntVolatile(o, offset);
05     while (!compareAndSwapInt(o, offset, v, v + delta));
06     return v;
07 }
08 public native int     getIntVolatile(Object o, long offset);
09 public final native boolean compareAndSwapInt(Object o, long offset,
10                                               int expected,
11                                               int x);

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

01 ...
02 import sun.misc.Unsafe;
03 public class AtomicTest {
04     ....
05     private Unsafe unsafe;
06     private long valueOffset;
07     public AtomicTest(){
08         Field f;
09         try {
10             f = Unsafe.class.getDeclaredField("theUnsafe");
11             f.setAccessible(true);
12             unsafe = (Unsafe)f.get(null);
13             valueOffset = unsafe.objectFieldOffset(AtomicInteger.class.getDeclaredField("value"));
14         }catch(NoSuchFieldException e){
15         ...
16         }
17     }
18     private final int getAndIncreaseD(){
19         int result;
20         do{
21             result = unsafe.getIntVolatile(ai, valueOffset);
22         }while(!unsafe.compareAndSwapInt(ai, valueOffset, result, result+1));
23         if(result == MAX){
24             System.out.println(System.nanoTime()-startTime);
25             System.exit(0);
26         }
27         return result;
28     }
29     ...
30 }

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

Unsafe是经过特殊处理的,不能理解成常规的java代码,区别在于:
在调用getAndAddInt的时候,如果系统底层支持fetch-and-add,那么它执行的就是native方法,使用的是fetch-and-add;
如果不支持,就按照上面的所看到的getAndAddInt方法体那样,以java代码的方式去执行,使用的是compare-and-swap;
这也正好跟openjdk8中Unsafe::getAndAddInt上方的注释相吻合:

1 // The following contain CAS-based Java implementations used on
2 // 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

相关文章
|
8月前
|
Java 编译器
解密Java多线程中的锁机制:CAS与Synchronized的工作原理及优化策略
解密Java多线程中的锁机制:CAS与Synchronized的工作原理及优化策略
|
8天前
|
存储 安全 Java
【Java EE】CAS原理和实现以及JUC中常见的类的使用
【Java EE】CAS原理和实现以及JUC中常见的类的使用
|
9月前
|
安全 Java
《Java-SE-第二十八章》之CAS
《Java-SE-第二十八章》之CAS
|
6月前
|
Java
面试java并发~(lock、volatile、cas)
面试java并发~(lock、volatile、cas)
43 0
|
7月前
|
缓存 算法 安全
JUC第四讲:Java中的锁/CAS原理与案例分析(下)
JUC第四讲:Java中的锁/CAS原理与案例分析
|
7月前
|
缓存 安全 算法
JUC第四讲:Java中的锁/CAS原理与案例分析(上)
JUC第四讲:Java中的锁/CAS原理与案例分析
JUC第四讲:Java中的锁/CAS原理与案例分析(上)
|
8月前
|
安全 算法 Java
Java多线程(3)---锁策略、CAS和JUC
Java多线程(3)---锁策略、CAS和JUC
49 0
|
8月前
|
Java
什么是 CAS?Java 中如何使用 CAS
什么是 CAS?Java 中如何使用 CAS
|
9月前
|
安全 Java
【Java|多线程与高并发】CAS以及ABA问题
CAS(Compare and Swap,“比较和交换”)是一种并发编程中常用的原子操作,用于解决多线程环境下的数据竞争和并发访问问题。
|
10月前
|
人工智能 移动开发 安全
【Java基础】CAS (Compare And Swap) 操作
CAS其实就是Compare And Swap的一个缩写,顾名思义就是比较并交换,其实就是把当前值与你预期的值进行一个比较,是一种用于在多线程环境下实现同步功能的机制。