话不多说,直接贴代码

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

    

  • 单线程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.指令重排序

    对主存的一次访问一般花费硬件的数百次时钟周期。处理器通过缓存(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变量,将不允许任何与其相关的操作进行指令重排序

 

https://www.cnblogs.com/Jasonchan1994/p/10696930.html

volatile关键字到底做了什么?相关推荐

  1. C++中mutable、volatile关键字

    C++中mutable.volatile关键字 mutable和volatile 很少遇到这两个关键字,学嵌入式估计知道后者,深入研究C++的估计知道前者. (1)mutable 在C++中,muta ...

  2. 就是要你懂 Java 中 volatile 关键字实现原理

    前言 我们知道volatile关键字的作用是保证变量在多线程之间的可见性,它是java.util.concurrent包的核心,没有volatile就没有这么多的并发类给我们使用. 本文详细解读一下v ...

  3. Java中volatile关键字实现原理

    原文地址http://www.cnblogs.com/xrq730/p/7048693.html,转载请注明出处,谢谢 前言 我们知道volatile关键字的作用是保证变量在多线程之间的可见性,它是j ...

  4. c语言中volatile关键字的作用

    读文章之前 可以先看一下<程序员的自我修养 >第28页 过度优化. volatile 提醒编译器它后面所定义的变量随时都有可能改变,因此编译后的程序每次需要存储或读取这个变量的时候,都会直 ...

  5. C语言volatile关键字详解

    volatile提醒编译器它后面所定义的变量随时都有可能改变,因此编译后的程序每次需要存储或读取这个变量的时候,都会直接从变量地址中读取数据.如果没有volatile关键字,则编译器可能优化读取和存储 ...

  6. 详解C中volatile关键字

    volatile提醒编译器它后面所定义的变量随时都有可能改变,因此编译后的程序每次需要存储或读取这个变量的时候,都会直接从变量地址中读取数据.如果没有volatile关键字,则编译器可能优化读取和存储 ...

  7. C语言volatile关键字的作用

    一.前言 1.编译器优化介绍: 由于内存访问速度远不及CPU处理速度,为提高机器整体性能,在硬件上引入硬件告诉缓存Cache,加速对内存的访问.另外在现代CPU中指令的执行并不一定严格按照顺序执行,没 ...

  8. [c++] volatile关键字

    [转]https://www.cnblogs.com/yc_sunniwell/archive/2010/06/24/1764231.html volatile提醒编译器它后面所定义的变量随时都有可能 ...

  9. c语言volatile关键字的作用是什么?

    一.前言 1.编译器优化介绍: 由于内存访问速度远不及CPU处理速度,为提高机器整体性能,在硬件上引入硬件高速缓存Cache,加速对内存的访问.另外在现代CPU中指令的执行并不一定严格按照顺序执行,没 ...

最新文章

  1. 用C语言解“BCD解密”问题
  2. 机器学习——Java调用sklearn生成好的Logistic模型进行鸢尾花的预测
  3. 输出对角线(输出格式控制)
  4. 7步让你get首个数据科学实习
  5. HDU1269 迷宫城堡(模板题)
  6. log4j 不同功能,同INFO级别,输出到不同log文件
  7. 「Photoshop 入门教程」了解 Photoshop 工作区
  8. 发生内部错误。请与 microsoft 支持部门联系。_设计变更、工程签证、确认单、工程洽商、联系单、会签的区别,一文搞懂...
  9. 如何查看计算机端口状态
  10. visio专业版svg图片裁剪
  11. HTML背景图片的设置
  12. 企业如何选择短信平台
  13. 1999年IT大盘点
  14. 再见2018 你好2019
  15. Linux磁盘与文件系统管理
  16. 云计算机教学,云计算机网络实验室,多媒体教室,
  17. Unity 编辑器开发实战【Custom Editor】- 为UI视图制作动画编辑器
  18. 使用docker安装RAP2心得
  19. asp.net Oracle数据库左侧目录树及右侧数据绑定及分页
  20. 常见js手撕题及算法总结

热门文章

  1. 《转》Python学习(16)-python异常
  2. 数据库连接字符串.udl
  3. 英文Ubuntu安装中文包(locale)的方法
  4. Pig安装与配置教程
  5. 用gnuplot画出c产生数据的波形图
  6. Leetcode 每日一题 40 组合2
  7. R学习_multitaper包解析2:子函数spec.mtm.dpss,dpssHelper
  8. Zero Copy 简介
  9. 学习Matlab强大的符号计算(解方程)
  10. c中嵌入Python,提供灵活性