volatile关键字在我们接触较多的几种语言C/C++/Java中均存在,在并发编程方面也会用到,因此还是有必要了解。
1 volatile关键字在java并发编程下的应用
Java中设置变量值的操作,除了long和double类型的变量外都是原子操作,关于long和double类型变量的非原子性问题,可参考《long和double类型变量的非原子性》一文。也就是说,对于变量值的简单读写操作没有必要进行同步。在JVM 1.2之前,Java的内存模型实现总是从主存读取变量,是不需要进行特别处理的。而随着JVM的成熟和优化,在当前的Java内存模型下,线程可以把变量保存在本地内存(比如寄存器)中,而不是直接在主存中进行读写。这就可能造成一个线程在主存中修改了一个变量的值,而另外一个线程还继续使用它在寄存器中的变量值的拷贝,会造成数据的不一致。要解决这个问题,只需要在程序中把该变量声明为volatile(不稳定的)即可,这就指示JVM这个变量是不稳定的,每次使用它都到主存中进行读取。一般说来,多任务环境下各任务间共享的标志都应该加volatile修饰。
volatile修饰的成员变量在每次被线程访问时,都强迫从共享内存中重读该成员变量的值。而且,当成员变量发生变化时,强迫线程将变化值回写到共享内存。这样在任何时刻,两个不同的线程总是看到某个成员变量的同一个值。 说白了,其实就是volatile关键字的变化可见的功能,当多个线程同时与某个对象交互时,就必须要注意到要让线程及时的得到共享成员变量的变化。Java语言规范中指出:为了获得最佳速度,允许线程保存共享成员变量的私有拷贝,而且只当线程进入或者离开同步代码块时才与共享成员变量的原始值对比。 那么,大家也注意到了,使用volatile屏蔽掉了VM中必要的代码优化,所以在效率上比较低。
使用建议:在两个或者更多的线程访问的成员变量上使用volatile。当要访问的变量已在synchronized代码块中,或者为常量时,不必使用。
为使大家更容易理解,结合C++下的例子,给大家做个演示。
2 C++下volatile关键字的使用案例
用volatile关键字声明的类型变量表示可以被某些编译器未知的因素更改,比如:操作系统、硬件或者其它线程等。遇到volatile关键字声明的变量,编译器对访问该变量的代码不再进行优化,从而可以提供对特殊地址的稳定访问。
使用该关键字的例子如下:
int volatile nVint;
当要求使用volatile 声明的变量的值的时候,系统总是重新从它所在的内存读取数据,即使它前面的指令刚刚从该处读取过数据。而且读取的数据立刻被保存。(这其实就是在第一部分中提到的每次使用都到主存中进行读取)。
例如:
volatile int i=10; int a = i; ... //其他代码,并未明确告诉编译器,对i进行过操作 int b = i;
volatile 指出 i是随时可能发生变化的,每次使用它的时候必须从i的地址中读取,因而编译器生成的汇编代码会重新从i的地址读取数据放在b中。而优化做法是,由于编译器发现两次从i读数据的代码之间的代码没有对i进行过操作,它会自动把上次读的数据放在b中。而不是重新从i里面读。这样以来,如果i是一个寄存器变量或者表示一个端口数据就容易出错,所以说volatile可以保证对特殊地址的稳定访问。
注意,在vc6中,一般调试模式没有进行代码优化,所以这个关键字的作用看不出来。下面通过插入汇编代码,测试有无volatile关键字,对程序最终代码的影响:
首先,用classwizard建一个win32 console工程,插入一个voltest.cpp文件,输入下面的代码:
#include <stdio.h> void main() { int i=10; int a = i; printf("i= %d\n",a); //下面汇编语句的作用就是改变内存中i的值,但是又不让编译器知道 __asm { mov dword ptr [ebp-4], 20h } int b = i; printf("i= %d\n",b); }
然后,在调试版本模式运行程序,输出结果如下:
i = 10 i = 32
然后,在release版本模式运行程序,输出结果如下:
i = 10 i = 10
输出的结果明显表明,release模式下,编译器对代码进行了优化,第二次没有输出正确的i值。下面,我们把 i的声明加上volatile关键字,看看有什么变化:
#include <stdio.h> void main() { volatile int i=10; int a = i; printf("i= %d\n",a); __asm { mov dword ptr [ebp-4], 20h } int b = i; printf("i= %d\n",b); }
分别在调试版本和release版本运行程序,输出都是:
i = 10 i = 32
这说明这个关键字发挥了它的作用!