// 给stop加上volatile后
public void run() {
int i = 0;
while (!stop) {
i++;
}
System.out.println("finish loop,i=" + i);
}
# {method} 'run' '()V' in 'VisibilityTest'
......
0x02b4895c: mov 0x4(%ebp),%ecx ; implicit exception: dispatches to 0x02b4899d
0x02b4895f: cmp $0x5dd5238,%ecx ; {oop('VisibilityTest')}
// 进入while判断
0x02b48965: jne 0x02b4898d ;*aload_0
; - VisibilityTest::run@2 (line 9)
// 跳转到0x02b48977获取stop
0x02b48967: jmp 0x02b48977
0x02b48969: nopl 0x0(%eax)
// 循环体内, i++
0x02b48970: inc %ebx ; OopMap{ebp=Oop off=49}
;*goto
; - VisibilityTest::run@12 (line 10)
0x02b48971: test %edi,0xb30000 ;*aload_0
; - VisibilityTest::run@2 (line 9)
; {poll}
// 循环过程中获取stop的值
0x02b48977: movzbl 0x64(%ebp),%eax ;*getfield stop
; - VisibilityTest::run@3 (line 9)
// 验证stop的值
0x02b4897b: test %eax,%eax
// 若stop不符合条件, 则继续跳转到0x02b48970: inc, 执行i++, 否则中断循环
0x02b4897d: je 0x02b48970 ;*ifne
; - VisibilityTest::run@6 (line 9)
0x02b4897f: mov $0x33,%ecx
0x02b48984: mov %ebx,%ebp
0x02b48986: nop
// 跳出循环, 执行System.out.print打印
0x02b48987: call 0x02b2cac0 ; OopMap{off=76}
;*getstatic out
; - VisibilityTest::run@15 (line 12)
; {runtime_call}
0x02b4898c: int3
0x02b4898d: mov $0xffffff9d,%ecx
......
再来看两个从Java语言规范中摘取的例子, 也是涉及到编译器优化重排, 这里不再做详细解释, 只说下结果.
例子1中有可能出现r2 = 2 并且 r1 = 1;
例子2中是r2, r5值因为都是=r1.x, 编译器会使用向前替换, 把r5指向到r2, 最终可能导致r2=r5=0, r4 = 3;
Happen-Before先行发生规则如果光靠sychronized和volatile来保证程序执行过程中的原子性, 有序性, 可见性, 那么代码将会变得异常繁琐.
JMM提供了Happen-Before规则来约束数据之间是否存在竞争, 线程环境是否安全, 具体如下:
顺序原则
一个线程内保证语义的串行性; a = 1; b = a + 1;
volatile规则
volatile变量的写,先发生于读,这保证了volatile变量的可见性,
锁规则
解锁(unlock)必然发生在随后的加锁(lock)前.
传递性
A先于B,B先于C,那么A必然先于C.
线程启动, 中断, 终止
线程的start()方法先于它的每一个动作.
线程的中断(interrupt())先于被中断线程的代码.
线程的所有操作先于线程的终结(Thread.join()).
对象终结
对象的构造函数执行结束先于finalize()方法.