首先声明:现在JVM经过优化,已不会出现liveness failure 。所以没事别用volatile。
在了解volatile之前,先介绍一个名词:liveness failure ,直译叫作活性失败。因为这是volatile很重要的一个应用场景:
public class volatileDemo {
private static boolean stopFlag;
public static void main(String[] args) throws InterruptedException {
Thread volatileThread =new Thread(){
@Override
public void run() {
while(!stopFlag){
System.out.print(Calendar.getInstance().get(Calendar.SECOND)+",");
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
volatileThread.start();
Thread.sleep(3000);
stopFlag=true;
}
}
运行几秒钟之后,发现并没有终止输出。(jre1.7之前会出现)
主线程修改了变量stopFlag,子线程B却没有感知,称为活性失败。
Java内存模型(JMM)规定了所有的变量都存储在主内存中,主内存中的变量为共享变量,而每条线程都有自己的工作内存,线程的工作内存保存了从主内存拷贝的变量,所有对变量的操作都在自己的工作内存中进行,完成后再刷新到主内存中。
代码stopFlag=true;主线程(线程main)虽然对stopFlag的变量进行了修改且刷新回主内存中(《深入理解java虚拟机》中关于主内存与工作内存的交互协议提到变量在工作内存中改变后必须将该变化同步回主内存),但volatileThread线程读的仍是自己工作内存的旧值导致出现多线程的可见性问题,解决办法就是给stopFlag变量加上volatile关键字。
据effective Java中描述,这个问题涉及到JVM对while(!flag)这种形式有一个提升的优化,即:
while(!flag){}
进行提升优化:
if(flag){
while(true){}
}
重点参考文章:
http://www.ibm.com/developerworks/cn/java/j-jtp06197.html
http://www.cnblogs.com/aigongsi/archive/2012/04/01/2429166.html
总结如下
- volatile重要工作是避免线程脏读:当线程对volatile变量进行读操作时,会先将自己工作内存中的变量置为无效,之后再通过主内存拷贝新值到工作内存中使用。
- volatile解决的是变量在多个线程之间的可见性,但不能完全保证数据的原子性。
- 现在JVM经过优化,已不会出现liveness failure 。所以没事别用volatile。