社区微信群开通啦,扫一扫抢先加入社区官方微信群
社区微信群
话不多说,直接贴代码
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内存模型中规定了所有的变量都存储在主内存中,每条线程还有自己的工作内存,线程的工作内存中使用到的变量需要到主内存去拷贝,线程对变量的所有操作(读取、赋值)都必须在工作内存中进行,而不能直接读写主内存中的变量。不同线程之间无法直接访问对方工作内存中的变量,线程间变量值的传递均需要在主内存来完成,线程、主内存和工作内存的交互关系如下图所示:
2.happen-before原则
Java语言中有一个“先行发生”(happen—before)的规则,它是Java内存模型中定义的两项操作之间的偏序关系,如果操作A先行发生于操作B,其意思就是说,在发生操作B之前,操作A产生的影响都能被操作B观察到,“影响”包括修改了内存中共享变量的值、发送了消息、调用了方法等,它与时间上的先后发生基本没有太大关系。这个原则特别重要,它是判断数据是否存在竞争、线程是否安全的主要依据。
下面是Java内存模型中的八条可保证happen—before的规则,它们无需任何同步器协助就已经存在,可以在编码中直接使用。如果两个操作之间的关系不在此列,并且无法从下列规则推导出来的话,它们就没有顺序性保障,虚拟机可以对它们进行随机地重排序。
3.指令重排序
对主存的一次访问一般花费硬件的数百次时钟周期。处理器通过缓存(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变量,将不允许任何与其相关的操作进行指令重排序
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!