作用
可以被当作轻量级的 synchronized,不需要加锁,保证可见性,禁止指令重排序,保证单个操作的原子性。
volatile 写的内存语义
当写一个 volatile 变量的时候,JMM 会把该线程对应的本地内存中的共享变量值刷新回主存。
volatile 读的内存语义
当读一个 volatile 变量的时候,JMM 会把该线程对应的本地内存置为无效,强制从主内存中读取。
禁止指令重排序的实现
通过 happens-before 规则来插入内存屏障禁止指令重排序,对于处理器重排序,JMM 的处理器重排序规则会要求 Java 编译器在生成指令序列的时,插入特定类型的内存屏障指令来禁止特定类型的处理器重排序。
如果 A happens-before B,则 A 操作所做的操作对 B 操作可见,并且按顺序 A 操作排在 B 操作的前面,并不要求 A 操作一定要在 B 操作之前执行。
happens-before 的 volatile 变量规则:对一个 volatile 域的写,happens-before 于任意后续对这个 volatile 域的读。
在每个 volatile 写操作的前面插入一个 StoreStore 屏障
在每个 volatile 写操作的后面插入一个 StoreLoad 屏障
在每个 volatile 读操作的后面插入一个 LoadLoad 屏障
在每个 volatile 读操作的后面插入一个 LoadStore 屏障
lock 前缀指令也相当于一个内存屏障(也称内存栅栏),内存屏障主要提供 3 个功能:
- 确保指令重排序时不会把其后面的指令排到内存屏障之前的位置,也不会把前面的指令排到内存屏障的后面;即在执行到内存屏障这句指令时,在它前面的操作已经全部完成;
- 强制将对缓存的修改操作立即写入主存,利用缓存一致性机制,并且缓存一致性机制会阻止同时修改由两个以上 CPU 缓存的内存区域数据;
- 如果是写操作,它会导致其他 CPU 中对应的缓存行无效。
可见性和单个写操作的原子性的实现原理
在 CPU 指令中通过 LOCK 前缀和缓存一致性协议保证可见性,LOCK 前缀指令会在执行指令的期间声言处理器的 LOCK# 信号,LOCK# 信号有两种实现方式,一种是锁总线,这种开销较大,会导致其他 CPU 不能访问总线,即不能访问系统内存。还有一种是锁缓存,锁定这块内存区域的缓存并回写到内存,并使用缓存一致性机制来确保修改的一致性,缓存一致性机制会阻止通知修改由两个以上处理器缓存的内存区域,当一个 CPU 核心嗅探到自己存储的共享变量的值被其他 CPU 核心更改时,会将自己本地内存保存的值置为无效。