Java8中CAS的增强

    xiaoxiao2024-05-14  117

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

    转载自 并发编程网 - ifeve.com 相关资源:JAVA上百实例源码以及开源项目源代码
    最新回复(0)