1.为什么要使用Volatile关键字?
先来看看一段代码:
package com.zy;
importjava.util.concurrent.TimeUnit;
public class VolatileTest {
private static boolean isRuning = true;
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
int i = 0;
while(VolatileTest.isRuning){
i++;
}
}
}).start();
try {
TimeUnit.SECONDS.sleep(1);
} catch(InterruptedException e) {
e.printStackTrace();
}
new Thread(new Runnable() {
@Override
public void run() {
isRuning = false; //设置is为false,使上面的线程结束while循环
System.out.println("设置isRuning为:false");
}
}).start();
}
}
代码很简单,启动两个线程,在主类中定一个一个全局的成员变量isRuning,线程一启动只要isRunning为true将持续i++,线程2将isRunning置为false,按照正常逻辑此时线程1也将停止,因为while中条件不成立了嘛。然而真是这样吗?
答案显然是:NO,整个程序依然在运行。
相信这种代码肯定有很多人写过,犯过这种错的人也不再少数。废话不多说,那么究竟是为什么会发生这种情况呢,我只给出比较浅显的解释,学艺有限,不再深究。
要解释这个问题就要从java线程的内存分配讲起,先来看一张(原文链接:http://www.cnblogs.com/nexiyi/p/java_memory_model_and_thread.html)
Java内存模型的主要目标是定义程序中各个变量的访问规则,即在虚拟机中将变量存储到内存和从内存中取出变量这样底层细节。此处的变量与Java编程时所说的变量不一样,指包括了实例字段、静态字段和构成数组对象的元素,但是不包括局部变量与方法参数,后者是线程私有的,不会被共享。
Java内存模型中规定了所有的变量都存储在主内存中,每条线程还有自己的工作内存(可以与前面将的处理器的高速缓存类比),线程的工作内存中保存了该线程使用到的变量到主内存副本拷贝,线程对变量的所有操作(读取、赋值)都必须在工作内存中进行,而不能直接读写主内存中的变量。不同线程之间无法直接访问对方工作内存中的变量,线程间变量值的传递均需要在主内存来完成,线程、主内存和工作内存的交互关系如下图所示,和上图很类似。
相信读了上面一段话大家也就大概明白了,线程中获取到所有的变量(示例程序中的isRunning)实际上只主内存的一个副本,线程2改变isRunning的值只是改变了副本的值,此时线程1如果要发现isRunning的值改变,首先线程2要将isRunning的值刷新的主内存,然后线程1要重新刷新isRunning的值。
至于线程中的变量副本的值如何刷新和何时刷新到主内存,和如何和何时从主内存中重新load值,本文不做概述。
所以说不同线程中的变量是不可见的!!!
2. Volatile作用
此时Volatile关键字出场了,它正是用来解决不同线程变量的可视性问题的。
如果你将一个变量申明Volatile的,那么只要对这个变量做出更改,那么其他的所有读操作就会看到这个更改。即使使用了本地缓存,情况也是如此,volatile所修饰的变量会立即被写入到主内存,而读操作就发生在主内存中。
3.Volatile使用场景
多个线程同时访问某个变量,那么这个变量就应该是volatile的,否则这个变量只能用Synchronize来同步访问。如果一个方法或者代码块完全是synchronize的,那么就不要volatile来修饰,因为同步方法数据会立即写入主内存,同时方法取值也是直接在主内存中取。
4.使用条件
4.1:一个变量值不依赖于它之前的值。比如:递增的计数器,++i
4.2:这个值不受其他变量值得限制。比如:Range类中的lower和upper边界必须遵守lower<upper
以上条件应该是要保证volatile变量的操作是原子操作。(何为原子操作:原子操作是不能被线程调度机制中断的操作,一旦操作开始,那么它一定可以在可能发生上下文切换之前(切换到其他线程之前)完成操作。)如果不是原子操作的话在完成一个操作中间可能包含多个指令,而这中间就可能发生上下文切换。在切换时其中的值已经被其他任务修改,此时再切换回来执行最后一条指令,所读取到的变量值可能已经被其他任务修改过。
如有不足之处请指出,菜鸟一枚,轻喷。
参考资料:
java编程思想第四版
http://www.cnblogs.com/nexiyi/p/java_memory_model_and_thread.html