volatile是用于向编译器传递某种信息的关键字。这种信息可以限制编译器对代码的优化,以免这些优化造成意想不到的后果。
一、编译器优化
对于现代编译器来说,常见的优化行为包括优化内存访问、加大并行程度等。
优化内存访问主要是减少对内存的访问次数。由于RAM的速度远远慢于CPU的执行速度,因此,在硬件层面上引入了cache和寄存器以减少访存,提高对指令和数据的访问速度。
加大指令并行程度就是经过编译器判断不会引起问题的条件下,调整指令的执行顺序,以尽可能多的利用CPU的指令流水线。
通常情况下,编译器的优化可以带来性能的提升,但是有些条件下则会造成错误。
二、volatile的作用
volatile的字面意思是“不稳定的,易变的”。它会通知编译器,声明为volatile的变量的值可能随时改变,不要对这个变量的访问进行优化。
具体来说,主要有下面几种情况。
1、不要优化掉对volatile的访问
int i;
int k;
int j;
.......
i=k;
i=j;
在上面的代码中,编译器可能会直接优化掉i=k,只保留i=j;如果对i的赋值有特殊含义(比如i代表某个硬件设备的指令寄存器),那么上面的优化就会造成错误。
volatile int i;
int k;
int j;
i=k;
i=j;
2、不要做变量的值不会改变的假设
编译器可以利用“数据流分析”技术,确定变量在那里赋值、在哪里使用、在哪里销毁,并依据此对变量的访问进行优化。例如,如果变量在某次被访问的时候加载到cache或者寄存器中,并且在当前线程中下一次访问之前没有修改过(编译器也只能确定当前线程的访问情况),那么下一次访问就会直接冲寄存器或者cache中获取。使用volatile后,每次对变量的访问都会从内存中取,而不会将其缓存在cache或者寄存器中。
但是,如果变量的值可能被另外的线程修改(编译器无法得知是否会被其他线程修改),那么寄存器或者cache中的数据就是过时的。这就有可能造成错误。
int flag=false;
while(!flag);
doSomething();
当flag被修改为true是,变回调用doSomething();flag由另外的线程修改。
但没有将flag声明为volatile的话,可能会出现下面的情况:对flag进行第一次访问时,将flag加载到寄存器中,由于在当前线程中没有对flag进行修改,之后每次判断,很有可能直接从寄存器中独出flag的值进行判断。毫无疑问这会造成死循环。