一、简介
- volatile是用来描述变量的内存可见性的关键字,使得对变量的读取每次都需要到主内存中去操作,但是不能保证变量操作的原子性,比如count++;
- 引入原因:
1)由于Java的内存模型分为工作内存和主内存,变量一般在主内存上分配,但是工作线程会将变量copy到工作内存上去执行;
2)这就可能会导致一个线程在主内存中修改了一个变量的值,而另一个线程还在继续使用它在寄存器中的值,会导致数据不一致,导致有些逻辑混乱
3)Java的语言规范指出,为了获取更好的性能,允许线程保存共享成员变量的私有拷贝,而且只有当线程进入或者离开同步代码块时,才将私有拷贝和共享内存中的值进行比较;
如下面的代码在执行后,将陷入死循环中:
public class VolatileExample implements Runnable {
private boolean flag = false;
@Override
public void run() {
System.out.println("The Thread " + Thread.currentThread().getName() + " begin.");
while(!flag){
}
System.out.println("The Thread " + Thread.currentThread().getName() + " end.");
}
private void setFlag(boolean flag){
this.flag = flag;
}
public static void main(String[] args) {
VolatileExample example = new VolatileExample();
new Thread(example).start();
try {
/**在我的机器上,设置的睡眠时间小于4ms时,可以正常结束*/
Thread.sleep(4);
example.setFlag(true);
System.out.println("The Thread " + Thread.currentThread().getName() + " modify the flag.");
Thread.sleep(1000);
System.out.println(example.flag);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
- 解决方法:
1)将变量设置为volatile,这样就告诉JVM该变量是不稳定的,每次使用它都需要到主内存中去读取;
2)上文注释地方,请注意当睡眠时间过小时(比如小于4ms),可以正常结束,说明可能在这个阶段正在将变量copy到工作内存中;说明如果及时修改变量的值,工作内存还是可以感知的;
原因是:线程的栈保存了线程运行时变量的值,当线程访问一个对象的时候,首先通过对象的引用去找到堆中的位置,然后将对象copy到工作内存中,然后对copy进行操作,在之后的某个时刻,再自动将变量副本写入到主内存中;
所以4ms可能是load、copy的时间;
二:Volatile功能
1.声明该变量为不稳定,每次读写需要对主内存中的变量进行操作;
2.volatile声明的变量不具有原子性,所以若是需要同步的对变量进行访问,还需要使用Atomic进行操作;
3.Java存储模型不会对volatile指令的操作进行指令重排序,这个保证对volatile变量的操作时时按照指令的出现顺序执行的;
指令重排序时,根据happen-before原则