自从jdk1.5以后,
volatile
可谓发生了翻天覆地的变化,从一个一直被吐槽的关键词,变成一个轻量级的线程通信代名词。
接下来我们将从以下几个方面来分析以下volatile
。
as if serial
的关系volatile
的特点volatile
的内存语义volatile
的使用场景重排序值得是编译器与处理器为了优化程序的性能,而对指令序列进行重新排序的。
但是并不是什么情况下都可以重排序的,
as if serial
简单的理解就是。不管怎么重排序,在单线程情况下程序的执行结果是一致。
根据 as if serial
原则,它强调了单线程。那么多线程发生重排序又是怎么样的呢?
请看下面代码
public class VolatileExample1 {
/**
* 共享变量 name
*/
private static String name = "init";
/**
* 共享变量 flag
*/
private static boolean flag = false;
public static void main(String[] args) throws InterruptedException {
Thread threadA = new Thread(() -> {
name = "yukong"; // 1
flag = true; // 2
});
Thread threadB = new Thread(() -> {
if (flag) { // 3
System.out.println("flag = " + flag + " name = " +name); // 4
};
});
}
}
上面代码中,name输出一定是yukong
吗,答案是不一定,根据happen-before
原则与as if serial
原则,由于 1、2不存在依赖关系,可以重排序,操作3、操作4也不存在数据依赖,也可以重排序。
那么就有可能发生下面的情况
1535967197003.png
上图中,操作1与操作2发生了重排序,程序运行的时候,线程A先将flag更改成true,然后线程B读取flag变量并且判断,由于此时flag已经是true,线程B将继续读取name的值,由于此时线程name的值还没有被线程A写入,那么线程此时输出的name就是初始值,因为在多线程的情况下,重排序存在线程安全问题。
volatile
变量具有以下的特点。
volatile
变量的读,总是能看到任意线程对这个变量的最后的修改。volatile
会禁止部分指令重排序。这里我先介绍一下volatile
关键词的特点,接下来我们将会从它的内存语义来解释,为什么它会具有以上的特点,以及它使用的场景。
volatile
变量时,JMM会立即将本地变量中对应的共享变量值刷新到主内存中。volatile
变量时,JMM会将线程本地变量存储的值,置为无效值,线程接下来将从主内存中读取共享变量。如果一个场景存在对volatile
变量的读写场景,在读线程B读一个volatile
变量后,,写线程A在写这个volatile
变量前所有的所见的共享变量的值都将会立即变得对读线程B可见。
那么这种内存语义是怎么实现的呢?
其实编译器生产字节码的时候,会在指令序列中插入内存屏障来禁止指令排序。下面就是JMM内存屏障插入的策略。
那么这些策略中,插入这些屏障有什么作用呢?我们逐条逐条分析一下。
根据这些策略,volatile变量禁止了部分的重排序,这样也是为什么我们会说volatile具有一定的有序的原因。
根据以上分析的volatile
的内存语义,大家也就知道了为什么前面我们提到的happen-before
原则会有一条
那么根据volatile
的内存语义,我们只需要更改之前的部分代码,只能让它正确的执行。
即把flag定义成一个volatile
变量即可。
public class VolatileExample1 {
/**
* 共享变量 name
*/
private static String name = "init";
/**
* 共享变量 flag
*/
private volatile static boolean flag = false;
public static void main(String[] args) throws InterruptedException {
Thread threadA = new Thread(() -> {
name = "yukong"; // 1
flag = true; // 2
});
Thread threadB = new Thread(() -> {
if (flag) { // 3
System.out.println("flag = " + flag + " name = " +name); // 4
};
});
}
}
我们来分析一下
1535969126569.png
下面我们看看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;
}
}
至于为何需要这么写请参考:
《Java 中的双重检查(Double-Check)》http://www.iteye.com/topic/652440