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. Rpm打包程序

    1.Rpm打包程序1.1为什么要使用rpm打包1.编译安装软件,优点是可以定制化安装目录.按需开启功能等,缺点是需要查找并实验出适合的编译参数,诸如MySQL之类的软件编译耗时过长.2.yum安装软件 ...

  2. php效率优化

    php效率优化 最近在公司一边自学一边写PHP程序,由于公司对程序的运行效率要求很高,而自己又是个新手,一开始就注意程序的效率很重要,这里就结合网上的一些资料,总结下php程序效率优化的一些策略:1. ...

  3. Netty的ChannelHandler,ChannelHandlerContext,ChannelPipeline

    本小节一起学习一下ChannelHandler,ChannelHandlerContext,ChannelPipeline这三个Netty常用的组件,不探究它们的底层源码,我们就简单的分析一下用法 首 ...

  4. django模版 常用变量

    在request中有很多有用的东西,如下: 获取当前用户: 1 {{ request.user }} 如果登陆就显示内容,不登陆就不显示内容: 1 2 3 4 5 {% if request.user ...

  5. 拼接两个yuv合帧

    http://blog.csdn.net/huahuahailang/article/details/9040847 /**************************************** ...

  6. 利用Console来学习、调试JavaScript

    一  什么是 Console Console 是用于显示 JS和 DOM 对象信息的单独窗口.并且向 JS 中注入1个 console 对象,使用该对象 可以输出信息到 Console 窗口中. 二  ...

  7. mongodb 的操作

    查找 db.collection的名字.find({"group":"kkkk","key":{$regex:/.*one.*/i}}); ...

  8. jar 运行报错:找不到或无法加载主类

    NIFEST.MF文件中指定的,如下所示:Manifest-Version: 1.0Class-Path: .Main-Class: com.webex.app.Main             // ...

  9. linux开机自检配置文件fstab变只读无法修改问题

    控制linux开机自检的配置文件是/etc/fstab,在最近用的服务器中,发现fstab变成了只读权限,无法修改. 解决方法:RH5下,因磁盘改变,而导致系统停在Ctrl+d,此时需输入密码进入修改 ...

  10. built-in SpecularType of Unity

    [built-in SpecularType of Unity] 1.声明变量. 注意并没有在Shader中声明_SpecColor,因为Lighting.cginc中已经帮我们声明. 2.声明使用B ...