why ?
Because:初始化对象的过程其实并不是一个原子的操作 。它会分为三部分执行 。
给 instance 分配内存
调用 instance 的构造函数来初始化对象
将 instance 对象指向分配的内存空间(执行完这步 instance 就为非 null 了)
步骤 2 和 3 不存在数据依赖关系 。如果虚拟机存在指令重排序优化 。则步骤 2和 3 的顺序是无法确定的 。如果A线程率先进入同步代码块并先执行了 3 而没有执行 2 。此时因为 instance 已经非 null 。这时候线程 B 在第一次检查的时候 。会发现 instance 已经是 非null 了 。就将其返回使用 。但是此时 instance 实际上还未初始化 。自然就会出错 。所以我们要限制实例对象的指令重排 。用 volatile 修饰(JDK 5 之前使用了 volatile 的双检锁是有问题的) 。
4. 原理
volatile 可以保证线程可见性且提供了一定的有序性 。但是无法保证原子性 。在 JVM 底层是基于内存屏障实现的 。
当对非 volatile 变量进行读写的时候 。每个线程先从内存拷贝变量到 CPU 缓存中 。如果计算机有多个CPU 。每个线程可能在不同的 CPU 上被处理 。这意味着每个线程可以拷贝到不同的 CPU cache 中
而声明变量是 volatile 的 。JVM 保证了每次读变量都从内存中读 。跳过 CPU cache 这一步 。所以就不会有可见性问题
对 volatile 变量进行写操作时 。会在写操作后加一条 store 屏障指令 。将工作内存中的共享变量刷新回主内存;
对 volatile 变量进行读操作时 。会在写操作后加一条 load 屏障指令 。从主内存中读取共享变量;
通过 hsdis 工具获取 JIT 编译器生成的汇编指令来看看对 volatile 进行写操作CPU会做什么事情 。还是用上边的单例模式 。可以看到
文章插图
PS:具体的汇编指令对我这个 Javaer 太南了 。但是 JVM 字节码我们可以认识 。的含义是给一个静态变量设置值 。那这里的,而且是第 17 行代码 。更加确定是给 instance 赋值了 。果然像各种资料里说的 。找到了据说还得翻阅 。这里可以看下这两篇 https://www.jianshu.com/p/6ab7c3db13c3 、 https://www.cnblogs.com/xrq730/p/7048693.html )
有 volatile 修饰的共享变量进行写操作时会多出第二行汇编代码 。该句代码的意思是对原值加零 。其中相加指令addl前有 lock 修饰 。通过查IA-32架构软件开发者手册可知 。lock前缀的指令在多核处理器下会引发两件事情:
将当前处理器缓存行的数据写回到系统内存
这个写回内存的操作会引起在其他CPU里缓存了该内存地址的数据无效
正是 lock 实现了 volatile 的「防止指令重排」「内存可见」的特性
5. 使用场景
您只能在有限的一些情形下使用 volatile 变量替代锁 。要使 volatile 变量提供理想的线程安全 。必须同时满足下面两个条件:
对变量的写操作不依赖于当前值
该变量没有包含在具有其他变量的不变式中
其实就是在需要保证原子性的场景 。不要使用 volatile 。
6. volatile 性能
volatile 的读性能消耗与普通变量几乎相同 。但是写操作稍慢 。因为它需要在本地代码中插入许多内存屏障指令来保证处理器不发生乱序执行 。
引用《正确使用 volaitle 变量》一文中的话:
很难做出准确、全面的评价 。例如 “X 总是比 Y 快” 。尤其是对 JVM 内在的操作而言 。(例如 。某些情况下 JVM 也许能够完全删除锁机制 。这使得我们难以抽象地比较和的开销 。)就是说 。在目前大多数的处理器架构上 。volatile 读操作开销非常低 —— 几乎和非 volatile 读操作一样 。而 volatile 写操作的开销要比非 volatile 写操作多很多 。因为要保证可见性需要实现内存界定(Memory Fence) 。即便如此 。volatile 的总开销仍然要比锁获取低 。
volatile 操作不会像锁一样造成阻塞 。因此 。在能够安全使用 volatile 的情况下 。volatile 可以提供一些优于锁的可伸缩特性 。如果读操作的次数要远远超过写操作 。与锁相比 。volatile 变量通常能够减少同步的性能开销 。
参考
《深入理解Java虚拟机》
http://tutorials.jenkov.com/java-concurrency/java-memory-model.htmlhttps://juejin.im/post/5dbfa0aa51882538ce1a4ebc
《正确使用 Volatile 变量》
https://www.ibm.com/developerworks/cn/java/j-jtp06197.htm
l
推荐阅读
- 游戏id取名鬼才 过目不忘的游戏id名字
- 丰满和胖有什么不一样?
- 适合8一12岁看的电影 适合孩子看的电影排行榜
- 有没有特别让你惊艳的古言,疯狂想安利给别人的那种?
- 最新全球城市人口排名 世界人口排名一览表图片
- 穿长筒靴搭配光腿神器是不是很难看?
- 腐剧泰国高颜值推荐 高颜值的五部泰国腐剧
- 说出你心中最伟大古代文学作品?理由是什么?
- 男人喜欢的到底是瘦一点的女生,还是有肉感的女生?