本文共 5836 字,大约阅读时间需要 19 分钟。
几天前,我偶然地将之前写的用来测试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,234jdk1.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的源码:
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