记一次内存可见性的深刻感悟

前言

  很多同学都知道,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;

  子线程再用原先的第一种写法,运行后程序是能终止的。

经验分享 程序员 微信小程序 职场和发展