线程安全可见性问题

public class VisibilityDemo {

    private boolean flag = true;

    public static void main(String[] args) throws InterruptedException {
        VisibilityDemo demo = new VisibilityDemo();
        System.out.println("开始运行");
        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                int i = 0;
                while (demo.flag){
                    i++;
                }
                System.out.println(i);
            }
        });
        thread1.start();
        TimeUnit.SECONDS.sleep(2);
        demo.flag = false;
        System.out.println("运行结束");
    }
}

如上,按照这段代码的目的来说,会在两秒后打印出 i 的值并结束。但运行后并不会结束,如下图



这就是一个可见性问题

那么问题就来了

  1. 这段程序为什么不会正常运行
  2. 如何能够让它正常运行

为什么不会正常运行

  1. 缓存导致的可见性问题
    每一个线程都有自己的本地变量,从内存中拷贝到CPU缓存,每个CPU核心有自己的缓存。但是CPU自身保证了缓存的一致性,缓存刷新会有延迟,但也是延迟一段时间 i 就会打印出来,不会一直不打印。

  2. JVM 的指令重排序
    JVM 的运行模式分为三种:编译模式、解释模式、混合模式
    编译模式:jit 将字节码提前编译为汇编
    解释模式:在程序运行时,一行一行的将字节码编译为汇编
    混合模式:运行过程中,对热点代码进行编译优化,与编译模式相同,其他非热点与解释模式相同
    在这个例子中,由于 jit 的优化导致程序没有按照预想的运行,优化后的程序相当于以下伪代码:

public void run() {
    int i = 0;
    boolean flag = demo.flag;
    while (flag){
        i++;
    }
    System.out.println(i);
}

指令重排序是在汇编阶段的操作,无法通过代码直接看到,不过我们可以在 jvm 的启动参数上加上

-Djava.compiler=NONE

关闭自动优化来验证


可以看到关闭后就打印出了 i 的值

如何修改代码使其正常运行

刚刚通过关闭 jit 的优化使其正常运行了,还可以通过修改代码让它正常运行

  1. volatile
private volatile boolean flag = true;

jvm 规范规定了,被 volatile 关键字修饰的变量不能够进行指令重排序,也不能被CPU缓存,修改的值实时地写入内存。在这里 volatile 可以解决这个问题。
注意:volatile 是语法层面的,并不是 volatile 直接起作用,而是 jvm 规范规定了,然后厂商按照规范开发虚拟机,是在虚拟机层面实现了 volatile 涉及的功能。

  1. synchronized

synchronized 关键字在这里不能解决这个问题,锁可以确保同一时间只有一个线程操作 i 变量,但这里不是多线程导致的,所以不能解决这个问题。

  1. lock

同 synchronized 也不能解决这个问题。

你可能感兴趣的:(线程安全可见性问题)