1. 双重校验锁实现单例的问题

在延迟实现单例时,一般代码形式如下:

 public class Foo {
private static volatile Foo instance; public static Foo getInstance() {
//第一次检查,不锁定
if (null == instance) {
//一旦初始化,第一次检查将无法通过,不会有锁定开销
synchronized (Foo.class) {
//第二次检查,锁定
if (null == instance) {
instance = new Foo();
}
}
}
return instance;
}
}

看起来很简单,但这里有个容易忽略的点,就是instance变量,需要用volatile修饰。

为什么?如果不加的话会有什么问题呢?

让我们把目光聚焦到第11行,初始化instance变量。这一行代码可以分解为如下3行伪代码。

memory = allocate(); //1. 分配对象的内存空间

ctorInstance(memory);//2. 初始化对象

instance = memory; //3.设置instance指向刚分配的内存地址

上面伪代码中的2和3,是可能会被重排序的,重排序后将变成如下时序:

memory = allocate(); //1. 分配对象的内存空间

instance = memory; //3.设置instance指向刚分配的内存地址,注意,此时对象未初始化。 

ctorInstance(memory);//2. 初始化对象

那么在多线程并发的场景下,假设有两条线程AB同时访问这个方法,可能发生以下的访问顺序:

时间                            线程A                                                     线程B

1                          分配内存空间(对应1)

2               设置instance指向内存空间(对应3)

3                                                                                    判断instance是否为null

4                                                                                 由于instance不为null, 不再等待进行临界区,直接访问instance引用的对象

5                           初始化对象(对应2)

这样一来,B在访问instance变量时,可能由于instance未初始化而导致出现一些异常。

那么,为什么加上volatile修饰就可以避免这种情况呢?这就涉及到happens-before和volatile的语义了。

2.happens-before与volatile语义

从JDK5开始,Java使用新的JSR-133内存模型。JSR-133使用happens-before的概念来阐述操作之间的内存可见性。在JMM中,如果一个操作执行的结果需要对另一个操作可见,以这两个操作之间必须要有happens-before关系。这里提到的两个操作,既可以是在一个线程之内,也可以是在不同的线程之间。

与程序员密切相关的happens-before规则如下。

1)程序顺序规则:一个线程中的每个操作,happens-before于该线程的任意后续操作。

2)监视器锁规则:对一个锁的解锁,happens-before于随后对这个锁的加锁。

3)volatile变量规则:对一个volatile域的写,happens-before于任意后续对这个volatile域的读。

4)传递性:如果A happens-before B,B happens-before C,那么A happens-before C.

理解volatile特性的一个好方法是把对volatile变量的单个读/写,看成是使用同一个锁对这些单个读/写操作做了同步。也就是说,对任意单个volatile变化的读/写具有原子性,但类似于volatile++这种复合操作不具有原子性。

那么上面第三条规则是怎么实现的呢?请参考:深入理解Java内存模型(四)——volatile

简单来说,就是在volatile写之后加入了一个StoreLoad屏障,防止后面的读与前面的写重排序了。这样后面的线程读到的,就是一个完整的对象。

参考:《Java并发编程的艺术》

正确理解volatile与happens-before的更多相关文章

  1. 理解volatile

    *:first-child { margin-top: 0 !important; } body>*:last-child { margin-bottom: 0 !important; } /* ...

  2. Java并发专题(三)深入理解volatile关键字

    前言 上一章节简单介绍了线程安全以及最基础的保证线程安全的方法,建议大家手敲代码去体会.这一章会提到volatile关键字,虽然看起来很简单,但是想彻底搞清楚需要具备JMM.CPU缓存模型的知识.不要 ...

  3. 5.彻底理解volatile

    1. volatile简介 在上一篇文章中我们深入理解了java关键字synchronized,我们知道在java中还有一大神器就是关键volatile,可以说是和synchronized各领风骚,其 ...

  4. 彻底理解volatile,领悟其中奥妙

    本人免费整理了Java高级资料,涵盖了Java.Redis.MongoDB.MySQL.Zookeeper.Spring Cloud.Dubbo高并发分布式等教程,一共30G,需要自己领取.传送门:h ...

  5. 让你彻底理解volatile,面试不再愁

    本人免费整理了Java高级资料,涵盖了Java.Redis.MongoDB.MySQL.Zookeeper.Spring Cloud.Dubbo高并发分布式等教程,一共30G,需要自己领取.传送门:h ...

  6. 深入理解volatile关键字

    Java内存模型 想要理解volatile为什么能确保可见性,就要先理解Java中的内存模型是什么样的. Java内存模型规定了所有的变量都存储在主内存中.每条线程中还有自己的工作内存,线程的工作内存 ...

  7. 对精致码农大佬的 [理解 volatile 关键字] 文章结论的思考和寻找真相

    一:背景 1. 讲故事 昨天在园里的编辑头条看到 精致码农大佬 写的一篇题为:[C#.NET 拾遗补漏]10:理解 volatile 关键字 (https://www.cnblogs.com/will ...

  8. 彻底理解volatile关键字

    1. volatile简介 在上一篇文章中我们深入理解了java关键字,我们知道在java中还有一大神器就是关键volatile,可以说是和synchronized各领风骚,其中奥妙,我们来共同探讨下 ...

  9. java并发编程(五)正确使用volatile

    转载请注明出处:     volatile用处说明     在JDK1.2之前,Java的内存模型实现总是从主存(即共享内存)读取变量,是不需要进行特别的注意的.而随着JVM的成熟和优化,现在在多线程 ...

随机推荐

  1. libvirt- Virsh 所有命令详单

    help            打印帮助    attach-device   从一个XML文件附加装置    attach-disk     附加磁盘设备    attach-interface 获 ...

  2. vs2010 安装 Ajax Control Toolkit

    Ajax Control Toolkit 7.1005.0 The Ajax Control Toolkit contains a rich set of controls that you can ...

  3. GeoServer之sqlserver插件使用

     GeoServer之sqlserver插件使用 安装好sqlserver插件后,点击出现的第一个选项,开始创建wms服务. 输入参数: 工作区:为geoserver中创建的工作区 数据源名称:自定义 ...

  4. leetcode697

    public class Solution { public int FindShortestSubArray(int[] nums) { //先找到最大频度的数字都有哪些,加入到一个集合中 var ...

  5. exited with code 1

    brcc32 command line for "Project1.vrc"   c:\program files\embarcadero\rad studio\9.0\bin\c ...

  6. 跟我学算法-tensorflow 实现线性拟合

    TensorFlow™ 是一个开放源代码软件库,用于进行高性能数值计算.借助其灵活的架构,用户可以轻松地将计算工作部署到多种平台(CPU.GPU.TPU)和设备(桌面设备.服务器集群.移动设备.边缘设 ...

  7. Hibernate面试问题集锦: 概述

    ImportNew注: 本文是ImportNew编译整理的Java面试题系列文章之一.你可以从这里查看全部的Java面试系列. Q.怎么配置Hibernate? A.Configuration类使用配 ...

  8. 读取resource下文件

    ArrayList<PatrolOper> patrolOpers = new ArrayList<>(); String jsonData = null; File json ...

  9. easyui datagrid combobox下拉框获取数据问题

    最近在使用easyui的datagrid,在可编辑表格中添加一个下拉框,查了下API,可以设置type : 'combobox',来做下拉框,这下拉框是有了,可是这后台数据怎么传过来呢,通过查API可 ...

  10. iOS设计模式(01):观察者

    iOS设计模式(01):观察者 iOS-Observer-Pattern 什么是观察者模式 什么是观察者模式?你曾经订阅过报纸吗?在订阅报纸的时候,你不用去任何地方,只需要将你的个人地址信息以及订阅信 ...