多线程的安全性问题(三)
上次文章中有讲到多线程带来的原子性问题,并且就原子性问题讲解了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;关于这个打印语句能解决可见性问题我要分两问分解答:
- 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.什么是可见性
在单线程的环境下,如果向一个变量先写入一个值,然后在没有写干涉的情况下读取这个变量的值,那这个时候读取到的这个变量的值应该是之前写入的那个值。这本来是一个很正常的事情。但是在多线程环境下,读和写发生在不同的线程中的时候,可能会出现:读线程不能及时的读取到其他线程写入的最新的值。这就是所谓的可见性