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. 如何卸载windows的服务?卸载服务?

    前面小编给大家介绍过如何禁用一些不需要的服务: 但是哪些多余的服务其实完成时可以直接卸载掉的: 所以今天小编将指导大家如何卸载一些不需要的服务: 切记请一定要确认卸载的是不需要的服务哦: 工具/原料 ...

  2. 【源码阅读】Java集合之一 - ArrayList源码深度解读

    Java 源码阅读的第一步是Collection框架源码,这也是面试基础中的基础: 针对Collection的源码阅读写一个系列的文章,从ArrayList开始第一篇. ---@pdai JDK版本 ...

  3. 转 Jquery实际应用,判断radio,selelct,checkbox是否选中及选中的值

    jquery取radio单选按钮的值   $("input[name='items']:checked").val();  另:判断radio是否选中并取得选中的值  如下所示: ...

  4. C++Primer笔记-----day01

    =======================================================day01======================================== ...

  5. GPU架构图

    找了几张GPU架构图,对理解图形渲染管线很有帮助

  6. C# StopWatch的BUG????

    //BUG?????? //使用StopWatch测试运行时间 //两段测试A和B //测试结果受测试顺序影响,后测要比先测耗时长了许多 static void TestKeyIntStr() { v ...

  7. Etcd的基本使用

    etcd 是 CoreOS 团队于 2013 年 6 月发起的开源项目,它的目标是构建一个高可用的分布式键值(key-value)数据库,基于 Go 语言实现,内部采用 raft 协议作为一致性算法. ...

  8. Django restframework Token拥有不过期的认证

    REST框架中的Token认证不像Session认证一样,它是没有办法设置过期时间的,但是有时我们需要对Token做过期验证,比如说用户在A设备登陆之后获取一个Token,如果用户在没有清空浏览器缓存 ...

  9. Logging模块总结 2018/5/30

    日志的级别 Level 用处 数字级别 DEBUG 详细的信息,在调试过程中用于诊断错误 10 INFO 用于确认事件正在运行 20 WARNING 意外发生时予以提醒,或者预测一些未来可能发生的一些 ...

  10. Android Studio 无法正确引入包内存在的类

    Android Studio 无法识别同一个 package 里的类,显示为红色,但是 compile 没有问题. 重启,rebuild,clean都没有用. 多半是因为 Android Studio ...