Volatile禁止指令重排
计算机在执行程序时候,为了提高性能,编译器和处理器常常会对编译器进程优化,一般分为以下三种
源代码->编辑器优化的重排–>指令并行的重排->内部系统工单重排–>最终执行指令
单线程环境里面确保最终执行结果和代码顺序的结构一致
处理器在进行重排时候,必须要考虑指令之间的数据依赖性
多线程环境中线程交替执行,由于编译器优化重排的存在,两个线程使用的变量能否安保证一致性无法确定,结果无法预测
指令重排的例子
public class ResortSeqDemo {
int a= 0;
boolean flag = false;
public void method01() {
System.out.println("进去方法一");
a = 1;
flag = true;
}
public void method02() {
if(flag) {
System.out.println("进去方法二");
a = a + 5;
System.out.println("reValue:" + a);
}
}
}
我们安装正常的顺序,分别调用方法method01()和method02(),安装这个顺序,最终的输出结果就是a的值为6
但是如果在多线程环境下,方法1和方法2不存在数据依赖性,因此原来执行的顺序可能是
a = 1;
flag = true;
a = a + 5;
System.out.println("reValue:" + a);
但是经过编译器执行重排后,可能会出现这样的情况
flag = true;
a = a + 5;
System.out.println("reValue:" + a);
a=1;
也就是想咨询完flag=true之后,另外的一个线程立马调用方法method02,满足flag的判断,最终执行a+5,结果为5,这样同样会出现数据不一致的问题
出现这个问题的原因是,多线程情况下,线程交替执行,由于编辑器优化重排的存在,两个线程在使用变量能否保证一致性是无法确定的,结果无法预测。这样就需要通过volatile来休息,保证线程的安全性
Volatile针对指令重排做了什么
在字段voaltile针对指令重排做了什么,我们需要先了解一个概念,内存屏障,是一个CPU指令,它的作用有两个
- 保证特定的操作顺序
- 保证某些变量内存可见性
由于编译器和处理器都能执行指令重排的优化,如果在指令间插入一条Memory Barrier则会告诉编译器和CPU,不管什么指令都不能和这条Memory Barrier指令重排序,也就是说,通过插入内存屏障禁止在内存屏障前后的指令执行重排序优化。内存屏障的另外一个作用是刷新各种CPU缓存数,因此任何CPU上的线程都能读取到这些数据的最新版本
内存屏障的作用
- 禁止指令重排
- 刷新缓存,任何CPU上的线程都能读取到这些数据的最新版本
- 也就是在volatile的写和读的时候,加入屏障,防止出现指令重排
线程安全获取保证
工作内存和主内存同步延迟现象导致可见性问题
- 可以通过synchronise或者volatile关键字解决,他们都可以让一个线程改变的变量立即对其他线程可见
对于指令重排导致的可见性问题和有序性问题
- 可以使用volatile关键字解决
说明参考
文章为看视频博客学习过程中总结,方便自己以后好复习
https://www.bilibili.com/video/BV18b411M7xz
http://moguit.cn/#/