考虑以下简单的Java应用程序:
public class Main {
public int a;
public volatile int b;
public void thread1(){
int b;
a = 1;
b = this.b;
}
public void thread2(){
int a;
b = 1;
a = this.a;
}
public static void main(String[] args) throws Exception {
Main m = new Main();
while(true){
m.a = 0;
m.b = 0;
Thread t1 = new Thread(() -> m.thread1());
Thread t2 = new Thread(() -> m.thread2());
t1.start();
t2.start();
t1.join();
t2.join();
}
}
}
QUESTION:读入局部变量是否可能得到thread1::b = 0
和thread2::a = 0
?
从JMM的Angular 来看,我无法证明这是不可能发生的,所以我开始分析x86-64
的编译代码.
以下是编译器为方法thread1
和thread2
生成的结果(为简单起见,省略了与While循环无关的代码和由-XX:+PrintAssembly
生成的一些注释):
thread1
:
0x00007fb030dca235: movl $0x1,0xc(%rsi) ;*putfield a
0x00007fb030dca23c: mov 0x10(%rsi),%esi ;*getfield b
thread2
:
0x00007fb030dcc1b4: mov $0x1,%edi
0x00007fb030dcc1b9: mov %edi,0x10(%rsi)
0x00007fb030dcc1bc: lock addl $0x0,0xffffffffffffffc0(%rsp) ;*putfield b
0x00007fb030dcc1c2: mov 0xc(%rsi),%esi ;*getfield a
因此,我们这里的情况是,volatile
次读取是免费完成的,volatile
次写入之后需要mfence
次(或lock add
次).
所以thread1
的存储在加载之后仍然可以被转发,因此thread1::b = 0
和thread2::a = 0
是可能的.