正确理解volatile与happens-before
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的更多相关文章
- 理解volatile
*:first-child { margin-top: 0 !important; } body>*:last-child { margin-bottom: 0 !important; } /* ...
- Java并发专题(三)深入理解volatile关键字
前言 上一章节简单介绍了线程安全以及最基础的保证线程安全的方法,建议大家手敲代码去体会.这一章会提到volatile关键字,虽然看起来很简单,但是想彻底搞清楚需要具备JMM.CPU缓存模型的知识.不要 ...
- 5.彻底理解volatile
1. volatile简介 在上一篇文章中我们深入理解了java关键字synchronized,我们知道在java中还有一大神器就是关键volatile,可以说是和synchronized各领风骚,其 ...
- 彻底理解volatile,领悟其中奥妙
本人免费整理了Java高级资料,涵盖了Java.Redis.MongoDB.MySQL.Zookeeper.Spring Cloud.Dubbo高并发分布式等教程,一共30G,需要自己领取.传送门:h ...
- 让你彻底理解volatile,面试不再愁
本人免费整理了Java高级资料,涵盖了Java.Redis.MongoDB.MySQL.Zookeeper.Spring Cloud.Dubbo高并发分布式等教程,一共30G,需要自己领取.传送门:h ...
- 深入理解volatile关键字
Java内存模型 想要理解volatile为什么能确保可见性,就要先理解Java中的内存模型是什么样的. Java内存模型规定了所有的变量都存储在主内存中.每条线程中还有自己的工作内存,线程的工作内存 ...
- 对精致码农大佬的 [理解 volatile 关键字] 文章结论的思考和寻找真相
一:背景 1. 讲故事 昨天在园里的编辑头条看到 精致码农大佬 写的一篇题为:[C#.NET 拾遗补漏]10:理解 volatile 关键字 (https://www.cnblogs.com/will ...
- 彻底理解volatile关键字
1. volatile简介 在上一篇文章中我们深入理解了java关键字,我们知道在java中还有一大神器就是关键volatile,可以说是和synchronized各领风骚,其中奥妙,我们来共同探讨下 ...
- java并发编程(五)正确使用volatile
转载请注明出处: volatile用处说明 在JDK1.2之前,Java的内存模型实现总是从主存(即共享内存)读取变量,是不需要进行特别的注意的.而随着JVM的成熟和优化,现在在多线程 ...
随机推荐
- leetcode680
class Solution { public: bool validPalindrome(string s) { int len = s.length(); ) return true; , len ...
- Django 实现用户认证set_Cookie
当用户通过认证时,set_Cookie(key, value) request.Cookie.get(key) 如果key不为空,就说明验证通过,否者重新跳转回login登录页面 对于URL urlp ...
- oracle、sqlserver、mysql常用函数对比[to_char、to_number、to_date]
Oracle --> MySQL to_char(sysdat ...
- MyBatis 学习记录5 MyBatis的二级缓存
主题 之前学习了一下MyBatis的一级缓存,主要涉及到BaseExecutor这个类. 现在准备学习记录下MyBatis二级缓存. 配置二级缓存与初始化发生的事情 首先二级缓存默认是不开启的,需要自 ...
- Server_id 冲突导致 IO 等待故障
问题描述: 线上添加新的 MySQL Slave 后,服务器异常. 1.show processlist; Queueing master event to the relay log Reconne ...
- linux: 空指令(:)
:指令 描述: 空命令,除了参数替换和重定向外不执行任何操作,总是保证退出码为0. eg1:创建文件(不需要调用其它程序,速度更快) :>/path/to/file 测试: 创建10000个不存 ...
- 764. Largest Plus Sign最大的dfs十字架
[抄题]: 求挖掉一些区域后,能允许出现的最大十字架 In a 2D grid from (0, 0) to (N-1, N-1), every cell contains a 1, except t ...
- Linux buffer and cache
A buffer is something that has yet to be "written" to disk. A cache is something that has ...
- 关于select Count()的使用和性能问题
比如Count(*) FROM E_Table WHERE [date] > '2008-1-1' AND istrue = 0 由于操作的数据比较大(400万以上),所以使用了两个数据库,一个 ...
- JSON_UNESCAPED_UNICODE
JSON_UNESCAPED_UNICODE(中文不转为unicode)