volatile关键字到底作了什么?

   话很少说,直接贴代码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的规则,它们无需任何同步器协助就已经存在,能够在编码中直接使用。若是两个操做之间的关系不在此列,而且没法从下列规则推导出来的话,它们就没有顺序性保障,虚拟机能够对它们进行随机地重排序。编码

    

  • 单线程happen-before原则:在同一个线程中,书写在前面的操做happen-before后面的操做。
  • 锁的happen-before原则:同一个锁的unlock操做happen-before此锁的lock操做。
  • volatile的happen-before原则:对一个volatile变量的写操做happen-before对此变量的任意操做(固然也包括写操做了)。
  • happen-before的传递性原则:若是A操做 happen-before B操做,B操做happen-before C操做,那么A操做happen-before C操做。
  • 线程启动的happen-before原则:同一个线程的start方法happen-before此线程的其它方法。
  • 线程中断的happen-before原则:对线程interrupt方法的调用happen-before被中断线程的检测到中断发送的代码。
  • 线程终结的happen-before原则:线程中的全部操做都happen-before线程的终止检测。
  • 对象建立的happen-before原则:一个对象的初始化完成先于他的finalize方法调用。

 

  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变量,将不容许任何与其相关的操做进行指令重排序

相关文章
相关标签/搜索