明天你会感谢今天奋力拼搏的你。
ヾ(o◕∀◕)ノヾ
在此先抛出结果:Java中i++不是原子操作,存在竞态条件,线程不安全。
这是为何?
在回答这个问题之前,先了解一个指令:javap -v -p Counter.class
javap是JDK自带的反编译工具,反解析出当前类对应的code区(汇编指令)、本地变量表、异常表和代码行偏移量映射表、常量池等等信息。
我们写一个简单的i++的代码示例:
public class Counter {
volatile int i = 0;
public void add(){
i++;
}
}
再进入Counter类的class文件所在目录,用上面的javap指令把class文件反编译一下,反编译的内容如下:
public class myTest.Counter
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #2.#3 // java/lang/Object."<init>":()V
#2 = Class #4 // java/lang/Object
#3 = NameAndType #5:#6 // "<init>":()V
#4 = Utf8 java/lang/Object
#5 = Utf8 <init>
#6 = Utf8 ()V
#7 = Fieldref #8.#9 // myTest/Counter.i:I
#8 = Class #10 // myTest/Counter
#9 = NameAndType #11:#12 // i:I
#10 = Utf8 myTest/Counter
#11 = Utf8 i
#12 = Utf8 I
#13 = Utf8 Code
#14 = Utf8 LineNumberTable
#15 = Utf8 LocalVariableTable
#16 = Utf8 this
#17 = Utf8 LmyTest/Counter;
#18 = Utf8 add
#19 = Utf8 SourceFile
#20 = Utf8 Counter.java
{
volatile int i;
descriptor: I
flags: ACC_VOLATILE
public myTest.Counter();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: aload_0
5: iconst_0
6: putfield #7 // Field i:I
9: return
LineNumberTable:
line 2: 0
line 3: 4
LocalVariableTable:
Start Length Slot Name Signature
0 10 0 this LmyTest/Counter;
public void add();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=3, locals=1, args_size=1
0: aload_0
1: dup
2: getfield #7 // Field i:I
5: iconst_1
6: iadd
7: putfield #7 // Field i:I
10: return
LineNumberTable:
line 6: 0
line 7: 10
LocalVariableTable:
Start Length Slot Name Signature
0 11 0 this LmyTest/Counter;
}
i是一个成员变量,在堆内存中。一个i++,在编译后会有4个指令:
(更多指令可以百度Java字节码指令https://www.cnblogs.com/longjee/p/8675771.html)
为什么这4个指令就能得出i++不是原子性?
按如上代码,如果是多线程操作,比如:2个线程同时执行了getfield指令,拿到相同的值都去加1然后放入堆内存中,那么2个线程应该加2的,其实就只加了1。
这不是可见性的问题,上面代码中i用volatile修饰,是没有用的。
如何解决保证i++的原子性?
前3种方式是相对简单和实用的,这里就不多做介绍了。在此通过代码简单介绍下调用Unsafe类实现i++的方法:
public class CounterUnsafe {
private int i = 0;
// Unsafe工具类对象
private static Object unsafe = null;
// 字段偏移量的值
private static long valueOffset;
static {
try {
// 通过反射获取Unsafe类
Class<?> unsafeClass = Class.forName("sun.misc.Unsafe");
Field field = unsafeClass.getDeclaredField("theUnsafe");
field.setAccessible(true);
unsafe = field.get(null);
//指定要修改的字段 i
Field iFiled = CounterUnsafe.class.getDeclaredField("i");
// 获得字段 i的偏移量
valueOffset = (long) unsafe.getClass().getMethod("objectFieldOffset",Field.class).invoke(unsafe, iFiled);
} catch (Exception e) {
e.printStackTrace();
}
}
public void add() throws Exception {
// 自旋调用unsafe的compareAndSwapInt方法对i进行+1
for(;;){
if ((boolean)unsafe.getClass().getMethod("compareAndSwapInt", Object.class, long.class, int.class, int.class)
.invoke(unsafe,this, valueOffset, i, i + 1)) {
return; // 执行成功返回
}
}
}
public static void main(String[] args) throws Exception {
CounterUnsafe counterUnsafe = new CounterUnsafe();
System.out.println(counterUnsafe.i);
counterUnsafe.add();
System.out.println(counterUnsafe.i);
}
}
全部评论