volatile关键字的作用

一、保证内存可见性

1、基本概念:volatile的本意是“异变的”因为访问寄存器要比访问内存单元快的多,所以编译器一般都会做减少存取内存的优化,但是可能会脏读数据。当要求使用volatile声明变量的时候,系统总是重新从它所在的内存读取数据,即使他前面的指令刚刚从该处读取过数据。从而保证了数据在内存的可见性。
eg1:

1、智能的编译器会注意下面代码使用了两次x,但并为改变它的值,于是编译器把x的值临时存储到寄存器中,然后在val2需要使用x时,从寄存器中读取x的值,而不是从原始的内存中读取,以节约时间,这个过程被称为高速缓存。
2、使用了volatile关键字修饰变量,之后使用该变量,就不会优化,直接从内存中读取。

val1 = x;
val2 = x;
eg2:
#include <iostream>
using namespace std;

//在C++中const修饰的常量不能被改变
//所以编译器优化作用把num放入寄存器中
//但要强制改变只改变内存中num中的值
//寄存器对应num的值并没有发生改变
//但是编译器去num的值时是在寄存器中取的
int main()
{
	const int num = 10;
	int *p = (int*)&num;
	*p = 20;
	printf("%d\n",num);
	return 0;
}


//在const修饰的变量前面加上volatile
//保证使用这个常量时每次从内存中拿
int main()
{
	volatile const int num = 10;
	int *p = (int*)&num;
	*p = 20;
	printf("%d\n",num);
	return 0;
}
2、多线程下的volatile

1、概念:线程之间的可见性,一个线程修改的状态对另一个线程可见,也就是一个线程修改的结果,另一个线程马上就能看到。
2、实现原理
当对非volatile变量进行读写的时候,每个线程先从主内存拷贝变量到CPU缓存中,如果计算机有多个CPU,每个线程可能在不同的CPU上被处理,这意味着每个线程可以拷贝到不同的CPU cache中。
volatile变量不会被缓存在寄存器或者对其他处理器不可见的地方,保证了每次读写变量从朱内存中读,跳过CPU cache这一步,当一个线程修改了这个变量的值,新值对于其他线程是立即得知的。
volatile关键字的作用
3、多线程下的有些变量是要volatile关键字声明的。当两个线程都要用到某一个变量且该变量的值被改变时,应该用volatile声明,该关键字的作用是防止优化编译器把变量从内存装入CPU寄存器中。如果变量被装入寄存器,那么两个线程有可能一个使用内存中的变量,一个使用寄存器中的变量,这会造成程序的徐哦呜执行。volatile的意思是让编译器每次操作该变量时一定要存内存中存取,而不是使用已经存在寄存器中的值。

votalie BOOL bStop = false;
(1)在一个线程中:
while(!bStop){...}
bStop = false;
return;

(2)在另一个线程中,要终止上面的线程循环
bstop = ture;
while(bStop);
//等待上面的线程终止,日过bStop不使用volatile声明,那么这个循环将是一个死循环,因为bStop已经已经读取到寄存器中,寄存器中的值永远不会变成false

二、禁止指令重排

由于内存访问速度远不及CPU处理速度,为提高机器整体性能,在硬件上引入硬件高速缓存Cache,加速对内存的访问。另外在现代CPU中指令的执行并不一定严格按照顺序执行,没有相关性的指令可以乱序执行,以充分利用CPU的指令流水线,提高执行速度。以上是硬件级别的优化。

double r = 2.1; //(1) 
double pi = 3.14;//(2) 
double area = pi*r*r;//(3)

虽然代码语句的定义顺序为1->2->3,但是计算顺序1->2->3与2->1->3对结果并无影响,所以编译时和运行时可以根据需要对1、2语句进行重排序。

三、使用场景(JAVA)

(1)volatile是轻量级同步机制。在访问volatile变量时不会执行加锁操作,因此也就不会使执行线程阻塞,是一种比synchronized关键字更轻量级的同步机制。
(2)volatile无法同时保证内存可见性和原子性。加锁机制既可以确保可见性又可以确保原子性,而volatile变量只能确保可见性
(3)volatile不能修饰写入操作依赖当前值的变量。声明为volatile的简单变量如果当前值与该变量以前的值相关,那么volatile关键字不起作用,也就是说如下的表达式都不是原子操作:“count++”、“count = count+1”。
(4)当要访问的变量已在synchronized代码块中,或者为常量时,没必要使用volatile;
(5)volatile屏蔽掉了代码中必要的代码优化,所以在效率上比较低,因此一定在必要时才使用此关键字。