举一个例子先来了解什么是内存可见性问题
当编译器频繁使用比较操作,执行这个操作的时候,会出现下面两个操作
LOAD(将内存中的数据读取到CPU寄存器中)和CMP(在CPU寄存器中进行比较操作),要注意,这个过程很明显是LOAD操作要慢很多,因为内存中执行读写操作效率比寄存器中要慢的多,这个时候编译器就会自动优化,"不读内存,只读寄存器",意思是当频繁执行比较操作的时候,将后续LOAD操作进行省略,只读取第一次就行了.因为理论上进行循环操作时,变量值并没有发生变化.
下面给出了出现内存可见性问题的情况
public class Demo9 {
static class Counter{
public int count = 0;//修饰变量,不会触发优化操作
}
public static void main(String[] args) {
Counter counter = new Counter();
Thread t1 = new Thread(() -> {
while(counter.count == 0){
}
System.out.println("t1 执行结束");
});
t1.start();//负责读操作
Thread t2 = new Thread(() -> {
System.out.println("请输入一个 int:");
Scanner scanner = new Scanner(System.in);
counter.count = scanner.nextInt();
});
t2.start(); //负责写操作
}
}
经过运行,当年输入的值将count值改正时,这时候程序仍然打印不了while循环中的下一个打印操作.
在这个过程中就发生了内存可见性问题,count只读取了开始的值(0).虽然后来count的值进行了修改,但是由于编译器优化的存在,不会读取修改后的值.所以就出现了线程不安全的问题
这个时候就需要使用volatile关键字,手动加上volatile语句的时候,这时候就会执行while循环下面的打印语句了,当然volatile不能保证原子性,只能保证内存可见性.
public volatile int count = 0;//修饰变量,不会触发优化操作
所以volatile禁止了编译器优化,避免了直接读取CPU寄存器中缓存的数据,而是每次都重新读取内存
JMM(Java内存模型)的角度看待volatile.
正常的程序执行的过程中,会把主内存的数据,先加载到工作内存中,在进行计算处理.编译器优化可能会导致不是每次都真的读取主内存,而是直接取工作内存中的缓存数据(可能会导致内存可见性问题)
volatile起到的效果就是保证内存都是真的从主内存中重新读取~