源码地址在github的ThreadsSafeProblem仓库demo1包。
一、问
内存中有一个整型变量value=0,两个线程都获取它后,将变量自加,会输出什么结果?
二、代码
UnsafeSequence.java
package demo1;
public class UnsafeSequence {
private int value;
public int getNextVal(){
return value++;
}
}
MyRunnable.java
package demo1;
public class MyRunnable implements Runnable {
UnsafeSequence unsafeSequence;
//向线程注入公用对象
public MyRunnable(UnsafeSequence unsafeSequence) {
this.unsafeSequence = unsafeSequence;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+":"+unsafeSequence.getNextVal());
}
}
Main.java
package demo1;
public class Main {
public static void main(String[] args) {
//定义公用对象
UnsafeSequence unsafeSequence = new UnsafeSequence();
//定义两个线程
MyRunnable m1 = new MyRunnable(unsafeSequence);
MyRunnable m2 = new MyRunnable(unsafeSequence);
Thread t1 = new Thread(m1);
Thread t2 = new Thread(m2);
//启动线程
t1.start();
t2.start();
}
}
三、选项
看完代码后,你觉得会输出什么呢?
- A. t1:0 和 t2:1
- B. t1:0 和 t2:0
- C. t1:1 和 t2:0
- D. t2:0 和 t1:1
- E. t2:0 和 t1:0
- F. t2:1 和 t1:0
答案:ABCDEF
四、分析原因
UnsafeSequence类中的value++操作,执行的过程中,大概可以分为取数、操作、写回3个操作,由于两个线程的这三个操作顺序的不确定性,所以上面程序输出的结果也有所不同。
如图这种状况,输出结果为t1:0 t2:0,至于其它情况可以多允许几次,估计就能看到不同的结果。
五、通过字节码分析
使用javap -v UnsafeSequence.class反编译查看字节码:
{
public demo1.UnsafeSequence();
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #10 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 3: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Ldemo1/UnsafeSequence;
public int getNextVal();
flags: ACC_PUBLIC
Code:
stack=4, locals=1, args_size=1
0: aload_0
1: dup
2: getfield #18 // Field value:I
5: dup_x1
6: iconst_1
7: iadd
8: putfield #18 // Field value:I
11: ireturn
LineNumberTable:
line 7: 0
LocalVariableTable:
Start Length Slot Name Signature
0 12 0 this Ldemo1/UnsafeSequence;
}
可以很清楚的看到,getNextVal方法做的0-11的操作,如果t1的putfield执行在t2的aload_0后,则t1/t2输出的结果都为0,否则一个为0一个为1。