Table of Contents
volatile
所具有的特性
1 控制可见性/一致性:对一个volatile变量的读,总能看到(任意线程)对这个volatile变量最后的写入。
2 原子性:读任意单个volatile变量的读、写具有原子性,但类似于volatile++ 这种复合操作不具有原子性。
Q:volatile是如何保证可见性的了? A:在多核处理器中,当进行一个volatile变量的写操作时,JIT编译器生成的汇编指令会在写操作的指令前加上一个“lock”前缀。“lock”前缀的指令在多核处理器下会引发了两件事情:
① 将当前处理器缓存行的数据会写回到系统内存。
② 这个写回内存的操作会引起在其他CPU里缓存了该内存地址的数据无效。
因此更确切的来说,因为操作缓存的最小单位为一个缓存行,所以每次对volatile变量自身的操作,都会使其所在缓存行的数据会写回到主存中,这就使得其他任意线程对该缓存行中变量的读操作总是能看到最新写入的值( 会从主存中重新载入该缓存行到线程的本地缓存中 )。
volatile写-读建立的happens before关系
happens-before 规则中有这么一条: volatile变量规则:对一个volatile域的写,happens-before于任意后续对这个volatile域的读。
happens-before的这个规则会保证volatile写-读具有如下的内存语义:
volatile写的内存语义: 当写一个 volatile 变量时,JMM 会把该线程对应的本地内存中的所有共享变量值刷新到主内存。 volatile读的内存语义: 当读一个 volatile 变量时,JMM 会把该线程对应的本地内存置为无效。线程接下来将从主内存中读取所有共享变量。 为了实现 volatile 的内存语义,编译器在生成字节码时,会在指令序列中插入内存屏障来禁止特定类型的处理器重排序。因为内存屏障是一组处理器指令,它并不由JVM直接暴露,因此JVM会根据不同的操作系统插入不同的指令以达成我们所要内存屏障效果。 从整体执行效率的角度考虑,JMM 选择了在每个 volatile 写的后面插入一个 StoreLoad 屏障。
StoreLoad屏障 指令示例:Store1; StoreLoad; Load2 确保Store1数据对其他处理器变得可见(指刷新到内存)先于Load2及所有后续装载指令的装载。 StoreLoad Barriers会使该屏障之前的所有内存访问指令(存储和装载指令)完成之后,才执行该屏障之后的内存访问指令。 StoreLoad Barriers是一个“全能型”的屏障,它同时具有其他3个屏障的效果(LoadLoad Barriers、StoreStore Barriers、LoadStore Barriers)
F.Y.I.
对volatile的思考: https://www.jianshu.com/p/55a66113bc54