参考博客:https://www.jianshu.com/p/8420ade6ff76
https://blog.csdn.net/javazejian/article/details/72772461
要说volatile关键字可见性的作用,需要先说一下关于计算机的设计的简化版:
当程序在运行过程中,会将运算需要的数据从内存复制一份到CPU的缓存当中,那么CPU进行计算时就可以直接从它的高速缓存读取数据和向其中写入数据,当运算结束之后,再将高速缓存中的数据刷新到主存当中。单核CPU只含有一套L1,L2,L3缓存;如果CPU含有多个核心,即多核CPU,则每个核心都含有一套L1(甚至和L2)缓存,而共享L3(或者和L2)缓存,按照数据读取顺序和与CPU结合的紧密程度,CPU缓存可以分为一级缓存(L1),二级缓存(L3),部分高端CPU还具有三级缓存(L3),每一级缓存中所储存的全部数据都是下一级缓存的一部分。
这只是计算机内存架构的一个简化版,具体请自行学习。
而JMM呢,更严格的来说,应该是jvm定义的一个规范,为了保证并发编程中可以满足原子性、可见性及有序性。
由于JVM运行程序的实体是线程,而每个线程创建时JVM都会为其创建一个工作内存,用于存储线程私有的数据,线程与主内存中的变量操作必须通过工作内存间接完成,主要过程是将变量从主内存拷贝的每个线程各自的工作内存空间,然后对变量进行操作,操作完成后再将变量写回主内存。如果存在两个线程同时对一个主内存中的实例对象的变量进行操作就有可能诱发线程安全问题。如下面的程序,主内存中存在一个共享变量x,现在有A和B两条线程分别对该变量isRunning 进行操作,首先新开启的线程将isRunning=true拷贝到自己的工作内存中,然后进行while循环 。然后主线程要修改isRunning,那如果在新启的线程将isRunning拷贝到自己的工作内存之后,主线程才将isRunning修改为了false,那while将一直循环,如果在拷贝之前,那么isRunning被新启线程刷新到工作内存的就是为fasle。这样就有可能造成主内存与工作内存间数据存在一致性问题,到底是哪种情况先发生呢?这是不确定的,又因为线程之间无法直接访问对方的工作内存中的变量,那如果是新开启的线程将isRunning=true拷贝到自己的工作内存中,那将无限循环下去。
那么,怎么解决缓存不一致性的问题呢,就是volatile关键字修饰变量,在这个变量修改后,会立刻通知其他线程,告诉它:我修改了变量,你重新读一下。
这就是volatile关键字的可见性
public class VolatileFace implements Runnable{
/*volatile*/ boolean isRunning = true;
@Override
public void run() {
System.out.println("isRunning start");
while(isRunning) {
}
System.out.println("isRunning end");
}
public static void main(String[] args) {
VolatileFace vf = new VolatileFace();
new Thread(vf).start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
vf.isRunning = false;
}
}