话很少说,直接贴代码java
class Singleton { private static volatile Singleton instance; private Singleton(){}
//双重判空 public static Singleton getInstance() { if ( instance == null ) { synchronized (Singleton.class) { if ( instance == null ) { instance = new Singleton(); } } } return instance; } }
这是一个你们耳熟能详的单例实现,其中有两个关键要点,一是使用双重检查锁定(Double-Checked Locking)来尽可能延迟加锁时间,以尽可能下降同步开销;二就是instance实例上加了volatile关键字。那么为何必定要加volatile关键字,volatile又为咱们作了什么事情呢?缓存
要了解这个问题,咱们先要搞清楚三个概念:java内存模型(JMM)、happen-before原则、指令重排序。安全
1.java内存模型(Java Memory Model)多线程
Java内存模型中规定了全部的变量都存储在主内存中,每条线程还有本身的工做内存,线程的工做内存中使用到的变量须要到主内存去拷贝,线程对变量的全部操做(读取、赋值)都必须在工做内存中进行,而不能直接读写主内存中的变量。不一样线程之间没法直接访问对方工做内存中的变量,线程间变量值的传递均须要在主内存来完成,线程、主内存和工做内存的交互关系以下图所示:app
2.happen-before原则性能
Java语言中有一个“先行发生”(happen—before)的规则,它是Java内存模型中定义的两项操做之间的偏序关系,若是操做A先行发生于操做B,其意思就是说,在发生操做B以前,操做A产生的影响都能被操做B观察到,“影响”包括修改了内存中共享变量的值、发送了消息、调用了方法等,它与时间上的前后发生基本没有太大关系。这个原则特别重要,它是判断数据是否存在竞争、线程是否安全的主要依据。优化
下面是Java内存模型中的八条可保证happen—before的规则,它们无需任何同步器协助就已经存在,能够在编码中直接使用。若是两个操做之间的关系不在此列,而且没法从下列规则推导出来的话,它们就没有顺序性保障,虚拟机能够对它们进行随机地重排序。编码
3.指令重排序spa
对主存的一次访问通常花费硬件的数百次时钟周期。处理器经过缓存(caching)可以从数量级上下降内存延迟的成本,这些缓存为了性能从新排列待定内存操做的顺序。也就是说,程序的读写操做不必定会按照它要求处理器的顺序执行。线程
JMM经过happens-before法则保证顺序执行语义,若是想要让执行操做B的线程观察到执行操做A的线程的结果,那么A和B就必须知足happens-before原则,不然,JVM能够对它们进行任意排序以提升程序性能。
基于以上三个概念,咱们能够拆解 instance = new Singleton() 这段代码:
// thread-A
memory = allocate(); // 1:分配对象的内存空间 ctorInstance(memory); // 2:初始化对象 instance = memory; // 3:设置instance指向刚分配的内存地址
然而,因为happen-before原则并不能保证这段代码的顺序性,这段代码可能被编译器优化为:
//thread-B
memory = allocate(); // 1:分配对象的内存空间 instance = memory; // 3:设置instance指向刚分配的内存地址 ctorInstance(memory); // 2:初始化对象
在单线程中不管是以哪一种顺序执行,都不会对结果有任何影响,然而在多线程下,有可能出现thread-B的执行顺序,尽管因为同步锁的存在,不会出现两个线程同时进入instance = new Singleton()的场景,可是若B线程执行完3以后,2尚未执行,CPU就切换时间片,执行一个全新的C线程,将致使C线程拿到一个非空的instance,然而这时候该instance尚未准备好。
而这一切,仅仅须要在instance实例前加上volatile,就能够完美的解决。
那么,volatile在例子中到底作了什么神奇的操做呢?
其一,对于volatile修饰的instance变量,若对instance的写操做执行在前,那么该写操做的结果必定会被马上刷新到主内存中,以后全部线程对于该instance的全部读写操做必然能够观察到最新的值,也即:volatile保证了变量的内存可见性
其二,对于volatile修饰的instance变量,将不容许任何与其相关的操做进行指令重排序