在之前提到:
通过引入并发来优化执行效率的同时引入了其他问题:
volatile 的作用在于保持相关操作的顺序来实现可见性与有序性。

变量修改对其他线程立即可见
https://www.pdai.tech/md/java/thread/java-thread-x-key-volatile.html#%E5%AE%9E%E7%8E%B0%E5%8F%AF%E8%A7%81%E6%80%A7
可见性问题主要指一个线程修改了共享变量值,而另一个线程却看不到。引起可见性问题的主要原因是每个线程拥有自己的一个高速缓存区——线程工作内存。volatile关键字能有效的解决这个问题,我们看下下面的例子,就可以知道其作用:
public class VolatileTest {
int a = 1;
int b = 2;
public void change(){
a = 3;
b = a;
}
public void print(){
System.out.println("b="+b+";a="+a);
}
public static void main(String[] args) {
while (true){
final VolatileTest test = new VolatileTest();
//开启两个线程,一个线程改变值,一个线程输出状态
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
test.change();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
test.print();
}
}).start();
}
}
}
直观上说,这段代码的结果只可能有三种:b=3;a=3 或 b=2;a=1或者b=2;a=3。不过运行上面的代码(可能时间上要长一点),你会发现除了上三种结果之外,还出现了第四种结果:b=3,a=1
明明无论如何a的变化都应该在b的变化之前,怎么会发生a没有变化,但是b却变化了
这个过程也可能是重排序以及Java主内存与工作内存之间的可见性问题,我们先来讨论可见性问题。

计算机系统中,为了尽可能地避免处理器访问主内存的时间开销,处理器大多会利用缓存(cache)以提高性能。其模型如下图所示:
https://tech.meituan.com/2014/09/23/java-memory-reordering.html
在之前我们分析过在并发下就会出现线程协同问题
对volatile变量的操作需要遵从先行发生原则,即如果对volatile变量A的写操作先于读操作发生,那么必然该写操作能够被读操作获取到。

这个过程的具体实现还是通过内存屏障,具体细节可查看:
其实就是通过直接写入内存(而不是通过工作内存)并告诉其他所有读操作该变量已有新值。
同理,volatile 变量规则属于先行发生原则的一部分,不允许被重排序。
(具体实现通过内存屏障)
Volatile通过内存屏障可以禁止指令重排序,内存屏障是一个CPU的指令,它可以保证特定操作的执行顺序。
内存屏障分为四种:
StoreStore屏障、StoreLoad屏障、LoadLoad屏障、LoadStore屏障。
JMM针对编译器制定了Volatile重排序的规则:

LoadLoad屏障:对于这样的语句Load1; LoadLoad; Load2,在Load2及后续读取操作要读取的数据被访问前,保证Load1要读取的数据被读取完毕。
StoreStore屏障:对于这样的语句Store1; StoreStore; Store2,在Store2及后续写入操作执行前,保证Store1的写入操作对其它处理器可见。
LoadStore屏障:对于这样的语句Load1; LoadStore; Store2,在Store2及后续写入操作被刷出前,保证Load1要读取的数据被读取完毕。
StoreLoad屏障:对于这样的语句Store1; StoreLoad; Load2,在Load2及后续所有读取操作执行前,保证Store1的写入对所有处理器可见。它的开销是四种屏障中最大的。在大多数处理器的实现中,这个屏障是个万能屏障,兼具其它三种内存屏障的功能。