Java volatile内存序及x86-64编译:线程局部变量取值可能性分析
线程局部变量b和a同时取0的可能性分析
给定以下Java程序,我们需要分析线程thread1的局部变量b取值为0,且线程thread2的局部变量a取值为0的情况是否可能发生:
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(); } } }
JMM层面分析
从Java内存模型(JMM)的角度来看,我们无法证明这种情况不会发生。JMM对普通变量和volatile变量的重排序规则有明确约束:volatile读操作仅会禁止后续的读写操作被重排序到它之前,但不会限制前面的普通写操作被重排序到它之后。因此thread1中a=1这个普通写操作,完全有可能被重排序到b=this.b这个volatile读操作之后执行。
x86-64架构编译代码验证
为进一步确认,我们查看该程序在x86-64架构下的汇编编译结果(省略无关代码和注释):
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读操作(thread1中读取
this.b)不需要额外的内存屏障指令; - volatile写操作(thread2中写入
this.b)后会插入lock addl指令(等效于mfence,起到内存屏障作用),确保自身之前的写操作对其他线程可见,但不会限制之后的普通读操作提前执行。
关键问题在于,thread1中的普通写a=1和volatile读b=this.b,在编译阶段或CPU层面仍然可能被重排序:thread1可能先执行b=this.b(此时this.b仍为0),再执行a=1;thread2则可能先执行a=this.a(此时this.a仍为0),再执行b=1。
结论
thread1的局部变量b取值为0,同时thread2的局部变量a取值为0的情况是可能发生的。
内容的提问来源于stack exchange,提问作者Some Name
相关产品推荐
相关产品推荐

