# {method} 'run''V'in 'VisibilityTest'......0x02d486e9: jne 0x02d48715// 获取stop的值0x02d486eb: movzbl 0x64(%ebp),%ecx ; implicit exception: dispatches to 0x02d487030x02d486ef: test %ecx,%ecx// 进入while之前, 若stop满足条件, 则跳转到0x02d48703, 不执行while循环0x02d486f1: jne 0x02d48703 ;* goto; - VisibilityTest::run@ 12(line 10)// 循环体内, i++0x02d486f3: inc %edi ; OopMap{ebp=Oop off= 52};* goto; - VisibilityTest::run@ 12(line 10)0x02d486f4: test %edi, 0xe00000;* goto; - VisibilityTest::run@ 12(line 10); {poll}// jmp, 无条件跳转到0x02d486f3, 一直执行i++操作, 根本不检查stop的值// 导致死循环0x02d486fa: jmp 0x02d486f30x02d486fc: mov $ 0x0,%ebp0x02d48701: jmp 0x02d486eb// 跳出循环0x02d48703: mov $ 0xffffff86,%ecx......
解决方案也很简单,只要给 stop 加上 volatile关键字。再次输出汇编代码,发现每次都会检查 stop 值,不再出现无限循环了。
// 给stop加上volatile后public void run {inti = 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 0x02b4899d0x02b4895f: cmp $0x5dd5238,%ecx ; {oop( 'VisibilityTest')}// 进入 while判断0x02b48965: jne 0x02b4898d;*aload_ 0; - VisibilityTest::run@2 (line 9)// 跳转到 0x02b48977获取stop0x02b48967: jmp 0x02b489770x02b48969: 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,%ecx0x02b48984: mov %ebx,%ebp0x02b48986: nop// 跳出循环, 执行System.out. print打印0x02b48987: call 0x02b2cac0; OopMap{off= 76};*getstatic out; - VisibilityTest::run@15 (line 12); {runtime_call}0x02b4898c: int30x02b4898d: 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 方法。返回搜狐,查看更多