前言
由于 Java 内存模型(JMM)规定,所有的变量都存放在主内存中,而每个线程都有着自己的工作内存(高速缓存)。
线程在工作时,需要将主内存中的数据拷贝到工作内存中。这样对数据的任何操作都是基于工作内存(效率提高),并且不能直接操作主内存以及其他线程工作内存中的数据,之后再将更新之后的数据刷新到主内存中。
所以在并发运行时可能会出现线程 B 所读取到的数据是线程 A 更新之前的数据。
作用
因此 volatile 的作用出现了:
当一个变量被 volatile 修饰时,任何线程对它的写操作都会立即刷新到主内存中,并且会强制让缓存了该变量的线程中的数据清空,必须从主内存重新读取最新数据。
内存可见性
当我们需要在两个线程间依据主内存通信时,通信的那个变量就必须用volatile
修饰。
public class VolatileDemo implements Runnable{
private static volatile boolean flag = true ;
@Override
public void run() {
while (flag){
System.out.println(Thread.currentThread().getName() + "执行中...");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() +"执行完毕");
}
public static void main(String[] args) throws InterruptedException {
VolatileDemo aVolatile = new VolatileDemo();
new Thread(aVolatile,"thread A").start();
System.out.println("main 线程正在运行") ;
Scanner sc = new Scanner(System.in);
while(sc.hasNext()){
String value = sc.next();
if(value.equals("1")){
new Thread(new Runnable() {
@Override
public void run() {
aVolatile.stopThread();
}
}).start();
break ;
}
}
System.out.println("主线程退出了!");
}
private void stopThread(){
flag = false ;
}
}
注意: volatile
不能保证线程安全性!
指令重排
volatile
还可以防止JVM进行指令重排,保证业务的正确性。
public class Singleton {
private static volatile Singleton singleton;
private Singleton() {
}
public static Singleton getInstance() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
//防止指令重排
singleton = new Singleton();
}
}
}
return singleton;
}
}
singleton = new Singleton();,这段代码其实是分为三步:
- 分配内存空间。(1)
- 初始化对象。(2)
- 将 singleton 对象指向分配的内存地址。(3)
加上 volatile 是为了让以上的三步操作顺序执行,反之有可能第二步在第三步之前被执行就有可能某个线程拿到的单例对象是还没有初始化的,以致于报错。