为了更好理解单例模式中的volatile之前,需要先简单了解下Java的内存模型。
Java的内存模型
Java内存模型简称JMM(Java Memory Model),是Java虚拟机所定义的一种抽象规范,用来屏蔽不同硬件和操作系统的内存访问差异,让java程序在各种平台下都能达到一致的内存访问效果。
Java内存模型是什么样子的呢?可以理解为下图的样子:
解释下:
- 1、主内存(Main Memory)
主内存可以简单理解为计算机当中的内存,但又不完全等同。主内存被所有的线程所共享,对于一个共享变量(比如静态变量,或是堆内存中的实例)来说,主内存当中存储了它的“本尊”。 - 2、工作内存(Working Memory)
工作内存可以简单理解为计算机当中的CPU高速缓存,但又不完全等同。每一个线程拥有自己的工作内存,对于一个共享变量来说,工作内存当中存储了它的“副本”。
线程对共享变量的所有操作都必须在工作内存进行,不能直接读写主内存中的变量。不同线程之间也无法访问彼此的工作内存,变量值的传递只能通过主内存来进行。原因是直接操作主内存速度较慢。 - 实例分析
说了这么多,大家是否很疑惑,为什么要讲这个??? 下面用赋值的代码案例,进行分析,加深理解。
static int s = 0;
public void test(){
if(s == 0){
s = 3;
System.out.println("初始化成功s=" + s);
}
}
在单线程A执行test方法时。打印输出s的结果
初始化成功s=3
结果和我们所想要的一样,但是如果引入多个线程,输出存在另一种可能:
初始化成功s=3
初始化成功s=3
为什么会出现两条结果呢?结合内存模型理解。工作内存所更新的变量并不会立即同步到主内存,所以虽然线程A在工作内存当中已经把变量s的值更新成3,但是线程B从主内存得到的变量s的值仍然是0,所以输出结果有两条。
为解决上诉问题的出现,java中引入了volatile关键字。
volatile关键字的使用理解
volatile关键字是一个轻量级的线程同步机制。只可以用来修饰变量,不可以修饰方法以及类。拥有两个特性:
- 保证了不同线程对该变量操作的内存可见性.
- 禁止了指令重排序.
对上述的可见性和指令重排序做下简单解释:
- 可见性:
当一个线程修改了变量的值,新的值会立刻同步到主内存当中。而其他线程读取这个变量的时候,也会从主内存中拉取最新的变量值。- 指令重排序:
指令重排是指JVM在编译Java代码的时候,或者CPU在执行JVM字节码的时候,对现有的指令顺序进行重新排序。指令重排的目的是为了在不改变程序执行结果的前提下,优化程序的运行效率。需要注意的是,这里所说的不改变执行结果,指的是不改变单线程下的程序执行结果。然而,指令重排是一把双刃剑,虽然优化了程序的执行效率,但是在某些情况下,会影响到多线程的执行结果。(如上面所举赋值的代码案例)
单例模式(双检锁方式)中的volatile
先上代码。
public class Singleton {
private volatile static Singleton singleton;
private Singleton (){}
public static Singleton getSingleton() { // 1
if (singleton == null) { // 2
synchronized (Singleton.class) { // 3
if (singleton == null) { // 4
singleton = new Singleton(); // 5
}
}
}
return singleton; // 6
}
}
很显然上面的代码如果没有使用volidate修饰。CPU为了优化执行效率,可能会出现重排序。从1-2-3 排序为1-3-2。
在多线程下就会出现问题,例如现在有2个线程A,B
线程A在执行第5的代码时,B线程进来,而此时A执行了 1和3,没有执行2,此时B线程判断instance不为null 直接返回一个未初始化的对象,就会出现问题.
而用了volatile,上面的重排序就会在多线程环境中禁止,不会出现上述问题.
volatile的使用场景
使用volatile修饰的变量最好满足以下条件:
- 对变量的写操作不依赖于当前值
- 该变量没有包含在具有其他变量的不变式中
这里举几个比较经典的场景:
- 状态标记量。如下
context = loadContext(); // (1)
inited = true; // (2)
- 一次性安全发布.双重检查锁定问题(单例模式的双重检查).
- 独立观察.如果系统需要使用最后登录的人员的名字,这个场景就很适合.
- 开销较低的“读-写锁”策略.当读操作远远大于写操作,可以结合使用锁和volatile来提升性能.