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;
}
}