对可变共享数据的访问如果不能同步,可能会出现预料之外的结果。但是下面这个例子就很容易导致误判,且很多帖子上的解释不是那么的准确。
public class StopThread {
private static boolean stopRequested;
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
int i=0;
while(!stopRequested) {
i++;
}
}
});
thread.start();
TimeUnit.SECONDS.sleep(1);
stopRequested = true;
}
}
上面这个例子大家再熟悉不过了,你可能会 觉得这个程序在运行大概1秒钟左右之后,主线程将stopRequested设置为true,使线程thread中的循环终止。但是结果却不会停止。
- 解决方式一:使用volatile修饰stopRequested变量;
- 解决方式二:在while循环中增加一行打印System.out.println("xxx"),打印任何东西都可以;
- 解决方式三:使用同步访问stopRequested;
原因分析:
- 方案一大家可能会觉得之所以修改之前的程序不会终止,是因为变量stopRequested,是线程不可见的,主线程修改之后,子线程无法获取修改后的值,其实这个解释是错误的;反例就是解决方案二;
- 方案二也可以解决子线程无法终止的问题,可以反向证明stopRequested不是因为子线程不可见导致的;
- 其实真正的原因是JVM 将while循环做了转变,即将代码
while(!stopRequested) { i++; }
转变成这样:
if(!stopRequested) { while(true) { i++; } }
这种优化称作提升(hoisting),这是HotShot JVM的工作模式。方案一中因为加了volatile之后,禁止JVM做指令重排序,不会出现上面的代码转变;方案二中增加打印也是为了禁止JVM进行代码的转变(因为println()方法是synchronized的);