并发场景下的数据可见性问题
Demo:线程1中flag如果为true就循环打印“线程1执行中”的语句,线程2启动后会将flag置为false
public class VolatileTest {private static boolean flag = true;public static void main(String[] args) {new Thread(() -> {System.out.println("线程1启动 flag:" + flag);while (flag) {System.out.println("线程1执行中 flag:" + flag);}System.out.println("线程1执行完成 flag:" + flag);}).start();new Thread(() -> {System.out.println("线程2 启动 flag:" + flag);flag = false;System.out.println("线程2 修改 flag:" + flag);}).start();}}
运行结果
线程1启动 flag:true 线程1执行中 flag:true 线程1执行中 flag:true 线程1执行中 flag:true 线程1执行中 flag:true 线程1执行中 flag:true
线程1执行中 flag:true
线程2 启动 flag:true
线程2 修改 flag:false
线程1执行中 flag:true
线程1执行完成 flag:false
从运行结果中看出当线程2将flag设置为false之后,线程1中的flag此时依旧为true
此时我们只需要用volatile关键字修饰一下flag变量即可
private volatile static boolean flag = true;
Java内存模型

每个线程都会有一块自己的独立内存,在线程工作的时候是先将数据从主内存中读取到自己的工作内存中,所以没有及时读取到最新的flag值。上述例子中,虽然线程2已经把flag的值设置为了false,但是由于线程1读的还是自己本地的值,所以为true。想要解决上述问题可以在修饰flag变量时加个volatile关键字
volatile语义
可见性
对被volatile关键字修饰的变量修改时,立即对其他线程可见。例如flag = true,当线程2把它设置为false后,线程1马上就可以读取到最新的flag=false。被volatile修饰的变量,在值被修改后,马上会执行store和write操作,并且过期掉其他所有缓存中的值,让数据重新从主内存load
禁止指令重排
禁止编译器将自认为可以进行重排序的指令进行重排
CPU缓存

由于从内存读取数据的速度比从cpu缓存中读取的速度慢很多,所以cpu在工作时,会先将数据从内存中读取到自己的缓存中再进行运算。但是由于缓存的数据所以可能会有数据一致性问题。但是java内存模型在硬件模型层面并不完全等价于cpu的缓存机制。cpu是靠MESI协议来保证缓存数据的一致性的
MESI协议
E(Exclusive)
数据有效,数据和内存中的数据一致,数据值存在于本CPU的cache中
S(Shared)
数据有效,数据和内存中的数据一致,数据存在于很多cache中
M(Modified)
数据有效,数据被修改了,和内存中的数据不一致,数据值存在于cpu的cache中


