首先说下为啥会有这篇文章,看了jdk1.8中CAS的增强,对其中关于AtomicInteger.getAndIncrement在jdk8中的优化深入了解了下,文中提到 “用反射获取到Unsafe实例,编写了跟getAndAddInt相同的代码,但测试结果却跟jdk1.7的getAndIncrement一样慢,不知道Unsafe里面究竟玩了什么黑魔法,还请高人不吝指点”。这就是由来。
这里有个小插曲,我们来看下getAndIncrement方法
jdk7, AtomicInteger.getAndIncrement(如下)
这个方法不需要多说了吧。
public final int getAndIncrement() {
for (;;) {
int current = get();
int next = current + 1;
if (compareAndSet(current, next))
return current;
}
}
看下jdk8的AtomicInteger.getAndIncrement和unsafe.getAndAddInt
public final int getAndIncrement() {
return unsafe.getAndAddInt(this, valueOffset, 1);
}
public final int getAndAddInt(Object paramObject, long paramLong, int paramInt) {
int i;
do
i = getIntVolatile(paramObject, paramLong);
while (!(compareAndSwapInt(paramObject, paramLong, i, i + paramInt)));
return i;
}
getIntVolatile的源码在这里sun.misc.natUnsafe.cc
jint
sun::misc::Unsafe::getIntVolatile (jobject obj, jlong offset)
{
volatile jint *addr = (jint *) ((char *) obj + offset);
jint result = *addr;
read_barrier ();
return result;
}
read_barrier是在此加个读屏障,不了解的复习下JVM内存模型
结论:通过jdk8中的getIntVolatile获取value值比通过jdk7中的get获取value值,能有更好的并发性能,jdk7中get获取的value在CAS时失败的几率更大
-------------------------------------------切入正题-----------------------------------------------
根据文中的思路我们测试了jdk8中的getAndIncrement与自己编写的与getAndAddInt相同的代码。比较发现直接调用getAndIncrement快三倍(在多线程版本中,这里特别强调,如果是单线程,getIntVolatile也没有意义)
然后我就在思考,这是为什么呢?
于是,我写了测试代码,发现差别不大
rt.jar 解压后的
public final int getAndAddInt(java.lang.Object, long, int);
descriptor: (Ljava/lang/Object;JI)I
flags: ACCPUBLIC, ACCFINAL
Code:
stack=7, locals=6, argssize=4
0: aload0
1: aload1
2: lload2
3: invokevirtual #35 // Method getIntVolatile:(Ljava/lang/Object;J)I
6: istore 5
8: aload0
9: aload1
10: lload2
11: iload 5
13: iload 5
15: iload 4
17: iadd
18: invokevirtual #36 // Method compareAndSwapInt:(Ljava/lang/Object;JII)Z
21: ifeq 0
24: iload 5
26: ireturn
LineNumberTable:
line 1031: 0
line 1032: 8
line 1033: 24
StackMapTable: numberofentries = 1
frametype = 0 /* same */
自己编写的,经过javac -g , javap -verbose
public final int getAndAddInt(java.lang.Object, long, int);
descriptor: (Ljava/lang/Object;JI)I
flags: ACCPUBLIC, ACCFINAL
Code:
stack=7, locals=6, argssize=4
0: getstatic #2 // Field unsafe:Lsun/misc/Unsafe;
3: aload1
4: lload2
5: invokevirtual #4 // Method sun/misc/Unsafe.getIntVolatile:(Ljava/lang/Object;J)I
8: istore 5
10: getstatic #2 // Field unsafe:Lsun/misc/Unsafe;
13: aload1
14: lload2
15: iload 5
17: iload 5
19: iload 4
21: iadd
22: invokevirtual #5 // Method sun/misc/Unsafe.compareAndSwapInt:(Ljava/lang/Object;JII)Z
25: ifeq 0
28: iload 5
30: ireturn
LineNumberTable:
line 46: 0
line 47: 10
line 48: 28
LocalVariableTable:
Start Length Slot Name Signature
0 31 0 this LAtomicIntegerByJJF;
0 31 1 paramObject Ljava/lang/Object;
0 31 2 paramLong J
0 31 4 paramInt I
10 21 5 i I
StackMapTable: numberofentries = 1
frametype = 0 /* same */
仔细寻找,发现最大的差别就是unsafe的调用,getAndAddInt调用unsafe是通过aload_0。而自己写的方法呢,由于unsafe限制,只能通过反射获取,故程序中调用unsafe是通过getstatic
但是在接下来的测试发现,aload_0与getstatic并不会导致上面三倍的差距(单线程下,实际测下来可以说不相伯仲)
----------------------------------------------补充结论--------------------------------------------------
看了这篇文章后AtomicInteger Java 7 vs Java 8 知道了为啥性能有差距了
unsafe的方法getAndAddInt,在执行时有专门的指令lock xadd。而反编译unsafe获得了getAndAddInt实现,然后自己重写一次,指令就变多了,没有直接调用快了。 so。。。。大概有3倍的性能差距。