上次文章中有讲到多线程带来的原子性问题,并且就原子性问题讲解了synchronized锁的本质以及用法,今天我们就着前面的内容跟着讲解,同样,我们在讲解前一样通过一个DEMO来引出今天的主题—–可见性问题

 

public class Volatlle {
public static boolean stop=false;

public static void main(String[] args) throws InterruptedException {
Thread thread=new Thread(()->{
int i=0;
while (!stop){
i++;
// System.out.println("结果:"+i);
// try {
// Thread.sleep(0);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
}
System.out.println("结果:"+i);
});
thread.start();
Thread.sleep(1000);
stop=true;
}
}

  我们运行上面代码,理论上分析我们可能会觉得应该会输出结果并且线程结束,但是我们看下图会发现现实与相像的差距,我们想要的输出一直没有出来,而且线程一直无法结束,导致这种现像的发生就是我们今天要讲的可见性问题。我们在外层加入一个值的变化,但子线程并不知道;为解决这种问题我们可以把public static boolean stop=false;加一个volatile关健字来解决;也可以在whie(!stop)中加入System.out.println(“结果:”+i);输出或者加个Thread.sleep(0)来解决可见性问题

 

 

 到了这里相信很多小伙伴们有问题了,我们就问题一个个解决,第一个问题System.out.println;关于这个打印语句能解决可见性问题我要分两问分解答:

  1. println底层用到了synchronized这个同步关键字,这个同步会防止循环期间对于stop值的缓存。

         2.因为println有加锁的操作,而释放锁的操作,会强制性的把工作内存中涉及到的写操作同步到主内存,可以通过如下代码去证明。

        3.从IO角度来说,print本质上是一个IO的操作,我们知道磁盘IO的效率一定要比CPU的计算效率慢得多,所以IO可以使得CPU有时间去做内存刷新的事情,从而导致这个现象。            比如我们可以在里面定义一个new File()。同样会达到效果。

第二个问题 :Thread.sleep(0)导致的可见性问题

     官方文档上是说,Thread.sleep没有任何同步语义,编译器不需要在调用Thread.sleep之前把缓存在寄存器中的写刷新到给共享内存、也不需要在Thread.sleep之后重新加载缓存在寄存器中的值。编译器可以自由选择读取stop的值一次或者多次,这个是由编译器自己来决定的。但是我个人的理解是Thread.sleep(0)导致线程切换,线程切换会导致缓存失效从而读取到了新的值。(文档位置:https://docs.oracle.com/javase/specs/jls/se8/html/jls-17.html#jls-17.3)

 

 一. volatile关键字(保证可见性)

     我们运行下面代码,然后在VM options:中通过汇编指令

-server -Xcomp -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly -XX:CompileCommand=compileonly,`*Volatlle.*`看到汇编指令lock指令

public class Volatlle {
    public volatile static boolean stop=false;

    public static void main(String[] args) throws InterruptedException {
        Thread thread=new Thread(()->{
            int i=0;
            while (!stop){
                i++;

            }
            System.out.println("结果:"+i);
        });
        thread.start();
        Thread.sleep(1000);
        stop=true;
    }
}

  

我们通过汇编发现,如果加了volatile关健值会多一个lock指令,这个指令决定了可见性问题。

    1.什么是可见性

      在单线程的环境下,如果向一个变量先写入一个值,然后在没有写干涉的情况下读取这个变量的值,那这个时候读取到的这个变量的值应该是之前写入的那个值。这本来是一个很正常的事情。但是在多线程环境下,读和写发生在不同的线程中的时候,可能会出现:读线程不能及时的读取到其他线程写入的最新的值。这就是所谓的可见性

 

 

版权声明:本文为xing1原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/xing1/p/13762974.html