单例模式中的volatile关键字

在之前学习了单例模式在多线程下的设计,疑惑为何要加volatile关键字。加与不加有什么区别呢?这里我们就来研究一下。单例模式的设计可以参考个人总结的这篇文章

  背景:在早期的JVM中,synchronized存在巨大的性能开销。因此,有人想出了一个“聪明”的技巧:双重检查锁定(Double-Checked Locking)。人们想通过双重检查锁定来降低同步的开销。下面是使用双重检查锁定来实现延迟初始化的示例代码。

public class DoubleCheckedLocking { // 1
private static Instance instance; // 2
public static Instance getInstance() { // 3
if (instance == null) { // 4:第一次检查
synchronized (DoubleCheckedLocking.class) { // 5:加锁
if (instance == null) // 6:第二次检查
instance = new Instance(); // 7:问题的根源出在这里
} // 8
} // 9
return instance; // 10
} // 11
}

上述的Instance类变量是没有用volatile关键字修饰的,会导致这样一个问题:

在线程执行到第4行的时候,代码读取到instance不为null时,instance引用的对象有可能还没有完成初始化。

主要的原因是重排序。重排序是指编译器和处理器为了优化程序性能而对指令序列进行重新排序的一种手段。

第7行的代码创建了一个对象,这一行代码可以分解成3个操作:

memory = allocate();  // 1:分配对象的内存空间
ctorInstance(memory); // 2:初始化对象
instance = memory;  // 3:设置instance指向刚分配的内存地址

根源在于代码中的2和3之间,可能会被重排序。例如:

memory = allocate();  // 1:分配对象的内存空间
instance = memory;  // 3:设置instance指向刚分配的内存地址
// 注意,此时对象还没有被初始化!
ctorInstance(memory); // 2:初始化对象

这在单线程环境下是没有问题的,但在多线程环境下会出现问题:



B线程会看到一个还没有被初始化的对象。



A2和A3的重排序不影响线程A的最终结果,但会导致线程B在B1处判断出instance不为空,线程B接下来将访问instance引用的对象。此时,线程B将会访问到一个还未初始化的对象。

所以只需要做一点小的修改(把instance声明为volatile型),就可以实现线程安全的延迟初始化。因为被volatile关键字修饰的变量是被禁止重排序的。

单例模式中的volatile关键字的更多相关文章

  1. Java中的volatile关键字的功能

    Java中的volatile关键字的功能 volatile是java中的一个类型修饰符.它是被设计用来修饰被不同线程访问和修改的变量.如果不加入volatile,基本上会导致这样的结果:要么无法编写多 ...

  2. zz剖析为什么在多核多线程程序中要慎用volatile关键字?

    [摘要]编译器保证volatile自己的读写有序,但由于optimization和多线程可以和非volatile读写interleave,也就是不原子,也就是没有用.C++11 supposed会支持 ...

  3. java中的volatile关键字

    java中的volatile关键字 一个变量被声明为volatile类型,表示这个变量可能随时被其他线程改变,所以不能把它cache到线程内存(如寄存器)中. 一般情况下volatile不能代替syn ...

  4. 深入理解Java中的volatile关键字

    在再有人问你Java内存模型是什么,就把这篇文章发给他中我们曾经介绍过,Java语言为了解决并发编程中存在的原子性.可见性和有序性问题,提供了一系列和并发处理相关的关键字,比如synchronized ...

  5. java中的Volatile关键字使用

    文章目录 什么时候使用volatile Happens-Before java中的Volatile关键字使用 在本文中,我们会介绍java中的一个关键字volatile. volatile的中文意思是 ...

  6. Java中的volatile关键字为什么不是不具有原子性

    Java中long赋值不是原子操作,因为先写32位,再写后32位,分两步操作,而AtomicLong赋值是原子操作,为什么?为什么volatile能替代简单的锁,却不能保证原子性?这里面涉及volat ...

  7. 从一个小例子引发的Java内存可见性的简单思考和猜想以及DCL单例模式中的volatile的核心作用

    环境 OS Win10 CPU 4核8线程 IDE IntelliJ IDEA 2019.3 JDK 1.8 -server模式 场景 最初的代码 一个线程A根据flag的值执行死循环,另一个线程B只 ...

  8. 面试中的volatile关键字

    在Java的面试当中,面试官最爱问的就是volatile关键字相关的内容.经过多次面试之后,你是否思考过,为什么他们那么爱问volatile关键字相关的问题?而对于你,如果作为面试官,是否也会考虑采用 ...

  9. C/C++中的volatile关键字

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

随机推荐

  1. 清空控件的TeXt属性

    foreach (Control item in groupBox1.Controls) { if (item is TextBox) //判断控件是不是TextBox { item.Text = & ...

  2. PL/SQL之包

    1.包的定义 一个包由两个独立的部分组成--包头和包体.给部分被单独地存放在数据字典中. .1定义包头 语法: CREATE [OR REPLACE] PACKAGE [schema.] packag ...

  3. C# 创建、部署、调用WebService

    webservice 可以用于分布式应用程序之间的交互,和不同程序之间的交互. 概念性的东西就不说太多,下面开始创建一个简单的webservice的例子.这里我用的是Visual Studio 201 ...

  4. 三分钟理解Java中字符串(String)的存储和赋值原理

    可能很多Java的初学者对String的存储和赋值有迷惑,以下是一个很简单的测试用例,你只需要花几分钟时间便可理解. 1.在看例子之前,确保你理解以下几个术语: 栈:由JVM分配区域,用于保存线程执行 ...

  5. apache-kylin 权威指南—读书笔记

    1. 概述 kylin 是 OLAP 引擎,采用多维立方体预计算技术,可将大数据的 SQL 查询速度提升到亚秒级别. 需求: 虽然像 spark,hive 等使用 MPP 大规模并行处理和列式存储的方 ...

  6. HDU 2276 Kiki & Little Kiki 2 矩阵构造

    Kiki & Little Kiki 2 Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java ...

  7. 用手机访问管理mysql

    移动办公的情况及需求越来越多,平时MySQL,Oracle,SQLServer等数据库的管理都要通过客户端工具操作,现在有一款基于web网页的软件:TreeSoft数据库管理系统,在服务器布署一套后, ...

  8. C#对Windows服务组的启动与停止

    Windows服务大家都不陌生,Windows服务组的概念,貌似MS并没有这个说法. 作为一名软件开发者,我们的机器上安装有各种开发工具,伴随着各种相关服务. Visual Studio可以不打开,S ...

  9. javaweb项目中绝对路径的写法理解

    Tomcat的默认访问路径为http://localhost:8080,后需添加项目路径. 请求转发,是转发到本项目中的其他文件,所以在默认访问路径中添加了本项目的项目路径,故可以省略项目名称: re ...

  10. kindeditor之video插件开发

    KindEditor是一套开源的HTML可视化编辑器,主要用于让用户在网站上获得所见即所得编辑效果.不仅结构小巧,而且功能强大,最主要的是它采用插件的开发管理方式,能很容易再它的基础上添加插件来实现自 ...