volatile 可见性
volatile关键字保证可见性: | 对一个被volatile关键字修饰的变量 |
---|---|
1 | 写操作的话,这个变量的最新值会立即刷新回到主内存中 |
2 | 读操作的话,总是能够读取到这个变量的最新值,也就是这个变量最后被修改的值 |
3 | 当某个线程收到通知,去读取volatile修饰的变量的值的时候,线程私有工作内存的数据失效,需要重新回到主内存中去读取最新的数据。 |
volatile 没有原子性
volatile 禁止重排
凭什么我们 Java 写了一个 volatile 关键字,系统底层加入内存屏障?两者的关系如何建立?
VolatileSeeDemo.java
public class VolatileSeeDemo {
volatile boolean flag;
public static void main(String[] args) {
System.out.println("Hello, Volatile!");
}
}
javap -v VolatileSeeDemo.class
对 .class
文件进行反编译
terminal
...
volatile boolean flag;
descriptor: Z
flags: (0x0040) ACC_VOLATILE
...
我们可以看到在 Class 内的 Field 的 flags
上增加了 ACC_VOLATILE
JVM在把字节码转换成机器码的时候,发现标记为 volatile 的变量,会按照 JMM 的规范,在相应的位置插入内存屏障。
内存屏障是什么?
是一种屏障指令,它使得CPU或编译器对屏障指令的前和后所发出的内存操作执行一个排序的约束。也称为内存栅栏或栅栏指令。
内存屏障能干吗?
- 阻止屏障两边的指令重排序
- 写操作时加入屏障,强制将线程私有工作内存的数据刷回主物理内存
- 读操作时加入屏障,线程私有工作内存的数据失效,重新回到主物理内存中获取最新值
内存屏障四大指令
-
在每一个volatile写操作前面插入一个StoreStore屏障
- 在每一个volatile写操作后面插入一个StoreLoad屏障
-
在每一个volatile读操作后面插入一个LoadLoad屏障
-
在每一个volatile读操作后面插入一个LoadStore屏障
对比 java.util.concurrent.locks.Lock
理解
cpu 执行机器码指令的时候,是使用 lock 前缀指令来实现 volatile 的功能的。
Lock 指令,相当于内存屏障,功能也类似内存屏障的功能:
- 首先对总线/缓存加锁,然后去执行后面的指令,最后释放锁,同时把高速缓存的数据刷新回到主内存。
- 在 lock 锁住总线/缓存的时候,其他 cpu 的读写请求就会被阻塞,直到锁释放。Lock 过后的写操作,会让其他 cpu 的高速缓存中相应的数据失效,这样后续这些 cpu 在读取数据的时候,就会从主内存去加载最新的数据
加了 Lock 指令过后的具体表现就和 JMM 添加内存屏障后一样。
总结
- volatile写之前的操作,都禁止重排序到volatile之后
- volatile读之后的操作,都禁止重排序到volatile之前
- volatile写之后volatile读,禁止重排序