volatile是Java中用来做同步的一个关键字,之前对它的作用一直理解得不是很透彻。
于是在网上查阅了一些资料,发现也讲得含混不清。
后来在wikipedia(http://en.wikipedia.org/wiki/Volatile_variable#In_Java)上看到了比较完善的解释。总的来说,volatile关键字是用来防止编译器做特定优化的,但具体作用取决于使用的语言(如C, C++, C#, Java)。
在Java中,volatile的作用有两点:
- 对volatile变量的修改能够立刻被其他线程知道,也就是说,在读取volatile变量的值时,不是从线程本地的cache读取,而是从主内存读取。这样就能保证多线程对同一个变量的读和写有一个全局的顺序。
- 可以防止编译器做额外的优化(如调整对变量的读写语句的执行顺序,对while循环的优化等)。
网上的资料经常会给出类似这样一个例子:
public class Thread1 extends Thread
{
private static boolean flag = false;
public void run(){
while(!flag)
{
// ...
}
}
public void close(){
flag = true;
}
}
public class Thread1 extends Thread
{
private static boolean flag = false;
public void run(){
while(!flag)
{
// ...
}
}
public void close(){
flag = true;
}
}
网上的说法是:线程A在执行run()方法中的while循环,这时线程B调用了close()方法,结果线程A停不下来。因为编译器观察到while循环中没有改变flag的值,就将while(!flag)优化成了while(true)。
开始我对编译器是否会优化到这种程度表示怀疑。因为据我实际测试,无论对flag是否加volatile关键字修饰,都是可以停下来的。据我的推测(根据上述volatile的作用第一点),线程A虽然不能在第一时间知道flag值的改变,但是最终还是会读到正确的值,从而停止while循环。
后来我去掉volatile关键字,再将while循环的执行次数变长(例如达到1000000000次),这时再调用close()方法,线程A不会停下来了。所以可能HotSpot在while循环执行的次数足够多时才会做while(true)的优化。
对于volatile的第二点作用,还有这样一个例子可以用来解释(来自http://www.cs.umd.edu/~pugh/java/memoryModel/jsr-133-faq.html#volatile):
public class VolatileExample {
int x = 0;
volatile boolean v = false;
public void writer() {
x = 42;
v = true;
}
public void reader() {
if (v == true) {
//uses x - guaranteed to see 42.
}
}
}
在注释处,我们希望看到的是x的值等于42。但是如果不加volatile修饰变量v,那么编译器有可能会调整writer()函数中两条语句的执行顺序,导致在注释处x的值不确定(也可能是0)。加上volatile后,便可以防止编译器为了优化随意调整语句的执行顺序。
与普通加锁方式的区别是:加锁机制既可以确保可见性又可以确保原子性,而volatile变量只能确保可见性。