记一次内存可见性的深刻感悟
前言
很多同学都知道,volatile关键字是来保证内存可见性的。那如果变量没有被volatile修饰,它一定是不可见的么?我们来一探究竟。
示例
public class MyVolatile { private boolean flag = true; public boolean getFlag() { return this.flag; } public void stopThread() { System.out.println(Thread.currentThread().getName() + ":stopThread"); flag = false; } }
public class Test { public static void main(String[] args) throws InterruptedException { MyVolatile myVolatile = new MyVolatile(); new Thread(() -> { //或者for(;;) {} while(true) { if(!myVolatile.getFlag()) { System.out.println(Thread.currentThread().getName() + "执行完毕"); break; } } }).start(); Thread.sleep(500); myVolatile.stopThread(); } }
看下结果:
程序没有终止:说明子线程拿到的myVolatile对象的flag属性,一直为true。此时flag属性是内存不可见的。
我们把子线程的执行代码修改一下,其余不变:
new Thread(() -> { while(myVolatile.getFlag()) { System.out.println(Thread.currentThread().getName() + "正在运行。。。"); } System.out.println(Thread.currentThread().getName() + "执行完毕"); }).start();
运行结果:
这里又行了,flag属性又可见了!!!那这是为什么呢?笔者也是查阅了相关资料,第一种while(true)的写法,如果循环体的执行效率非常高的话,执行时就不会再去获取flag属性的最新值了,即不可见了;而第二种写法,在while中加入了判断:flag属性是否为true,所以每次循环前都要去获取flag属性的最新值,此时就是可见的。 现在把第一种写法的循环体改动一下,不要让它的执行效率那么高:
new Thread(() -> { //或者for(;;) {} while(true) { if(!myVolatile.getFlag()) { System.out.println(Thread.currentThread().getName() + "执行完毕"); break; } try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } }).start();
或者使用同步方法:
Object o = new Object(); new Thread(() -> { //或者for(;;) {} while(true) { synchronized (o) { if (!myVolatile.getFlag()) { System.out.println(Thread.currentThread().getName() + "执行完毕"); break; } } } }).start();
再看运行结果:
此时flag属性是可见的了。但这2种是不是性能就低下了,这时volatile关键字就派上用场了,把它加到flag属性上:
private volatile boolean flag = true;
子线程再用原先的第一种写法,运行后程序是能终止的。
下一篇:
unidbg 应用实例,失败的实验