723
技术社区[云栖]
Java8中CAS的增强
几天前,我偶然地将之前写的用来测试AtomicInteger和synchronized的自增性能的代码跑了一下,意外地发现AtomicInteger的性能比synchronized更好了,经过一番原因查找,有了如下发现:
在jdk1.7中,AtomicInteger的getAndIncrement是这样的:
01 |
public final int getAndIncrement() {
|
04 |
int next = current + 1 ;
|
05 |
if (compareAndSet(current, next))
|
09 |
public final boolean compareAndSet( int expect, int update) {
|
10 |
return unsafe.compareAndSwapInt( this , valueOffset, expect, update);
|
而在jdk1.8中,是这样的:
1 |
public final int getAndIncrement() {
|
2 |
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的性能有了大幅提升。
03 |
import java.util.concurrent.CountDownLatch;
|
04 |
import java.util.concurrent.atomic.AtomicInteger;
|
05 |
import java.util.concurrent.locks.LockSupport;
|
07 |
public class AtomicTest {
|
08 |
//测试规模,调用一次getAndIncreaseX视作提供一次业务服务,记录提供TEST_SIZE次服务的耗时
|
09 |
private static final int TEST_SIZE = 100000000 ;
|
11 |
private static final int THREAD_COUNT = 10 ;
|
12 |
//使用CountDownLatch让各线程同时开始
|
13 |
private CountDownLatch cdl = new CountDownLatch(THREAD_COUNT + 1 );
|
16 |
private AtomicInteger ai = new AtomicInteger( 0 );
|
17 |
private long startTime;
|
20 |
startTime = System.nanoTime();
|
24 |
* 使用AtomicInteger.getAndIncrement,测试结果为1.8比1.7有明显性能提升
|
27 |
private final int getAndIncreaseA() {
|
28 |
int result = ai.getAndIncrement();
|
29 |
if (result == TEST_SIZE) {
|
30 |
System.out.println(System.nanoTime() - startTime);
|
37 |
* 使用synchronized来完成同步,测试结果为1.7和1.8几乎无性能差别
|
40 |
private final int getAndIncreaseB() {
|
45 |
if (result == TEST_SIZE) {
|
46 |
System.out.println(System.nanoTime() - startTime);
|
53 |
* 使用AtomicInteger.compareAndSet在java代码层面做失败重试(与1.7的AtomicInteger.getAndIncrement的实现类似),
|
57 |
private final int getAndIncreaseC() {
|
61 |
} while (!ai.compareAndSet(result, result + 1 ));
|
62 |
if (result == TEST_SIZE) {
|
63 |
System.out.println(System.nanoTime() - startTime);
|
69 |
public class MyTask implements Runnable {
|
75 |
} catch (InterruptedException e) {
|
79 |
getAndIncreaseA(); // getAndIncreaseB();
|
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" );
|
以下是在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)
|
05 |
j = getIntVolatile(obj, l);
|
06 |
while (!compareAndSwapInt(obj, l, j, j + i));
|
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) {
|
04 |
v = getIntVolatile(o, offset);
|
05 |
} while (!compareAndSwapInt(o, offset, v, v + delta));
|
08 |
public native int getIntVolatile(Object o, long offset);
|
09 |
public final native boolean compareAndSwapInt(Object o, long offset,
|
我的测试代码(提示:如果eclipse等ide报错,那是因为使用了受限的Unsafe,可以将警告级别从error降为warning,具体百度即可):
02 |
import sun.misc.Unsafe;
|
03 |
public class AtomicTest {
|
05 |
private Unsafe unsafe;
|
06 |
private long valueOffset;
|
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){
|
18 |
private final int getAndIncreaseD(){
|
21 |
result = unsafe.getIntVolatile(ai, valueOffset);
|
22 |
} while (!unsafe.compareAndSwapInt(ai, valueOffset, result, result+ 1 ));
|
24 |
System.out.println(System.nanoTime()-startTime);
|
补充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的特殊处理也就是我上文所说的“黑魔法”。
相关链接:
https://ashkrit.blogspot.com/2014/02/atomicinteger-java-7-vs-java-8.html
https://hg.openjdk.java.net/jdk8u/hs-dev/jdk/file/a006fa0a9e8f/src/share/classes/sun/misc/Unsafe.java
最后更新:2017-05-22 16:37:50