1、为什么要使用volatile关键字?
先看下面的代码:
//线程1 boolean stop = false; while(!stop){ doSomething(); } //线程2 stop = true;
事实上,这段代码会完全运行正确么?即一定会将线程中断么?不一定。
解释:
每个线程在运行过程中都有自己的工作内存,那么线程1在运行的时候,会将stop变量的值拷贝一份放在自己的工作内存当中。
那么当线程2更改了stop变量的值之后,但是还没来得及写入主存当中,线程2转去做其他事情了,那么线程1由于不知道线程2对stop变量的更改,因此还会一直循环下去。
为了解决这种问题,引入volatile关键字;
2、volatile关键字使用的特点:
1、使用volatile关键字会强制将修改的值立即写入主存。
2、使用volatile关键字的话,当线程2进行修改时,会导致线程1的工作内存中缓存变量stop的缓存行无效。
3、由于线程1的工作内存中缓存变量stop的缓存行无效,所以线程1再次读取变量stop的值时会去主存读取。
4、不能保证原子性只保证的是可见性、能保证有序性。
5、由于有些时候对 volatile的操作,不会被保存,说明不会造成阻塞。不可用与多线程环境下的计数器。
原子性测试:
package thread; public class Test { public volatile int inc = 0; public void increase() { inc++; } public static void main(String[] args) { final Test test = new Test(); for(int i=0;i<10;i++){ new Thread(){ public void run() { for(int j=0;j<1000;j++) test.increase(); }; }.start(); } while(Thread.activeCount()>1) //保证前面的线程都执行完 Thread.yield(); System.out.println(test.inc); } }
结果:
值总是小于10000;即可说明其不满足原子性。
有序性解释:
a=0;//1 b=100;//2 falg=true;//3 c=20;//4 d=39;//5 e=49;//6
falg是volatile变量,其余变量不是;
这段程序能够保证,3在其执行前,1,2均执行完,但1,2二者执行顺序如何,3不能保证。
4,5,6执行前,3一定执行完,且3之前的所有结果对3以后的代码可见,至于4,5,6的执行顺序如何,3保证不了。
可见性的理解:
当对一个volatile变量进行写操作的时候,JMM会把该线程对应的本地内存中的共享变量的值刷新到主内存中。
当读一个volatile变量的时候,JMM会把该线程对应的本地内存设置为无效,要求线程从主内存中读取数据。
3、何时使用volatile关键子
1、作状态标记量
volatile boolean inited = false; //线程1: context = loadContext(); inited = true; //线程2: while(!inited ){ sleep() } doSomethingwithconfig(context);
volatile boolean flag = false; while(!flag){ doSomething(); } public void setFlag() { flag = true; }
2、double check
class Singleton{ private volatile static Singleton instance = null; private Singleton() { } public static Singleton getInstance() { if(instance==null) { synchronized (Singleton.class) { if(instance==null) instance = new Singleton(); } } return instance; } }