🟠 volatile
2022年6月20日
- Java
🟠 volatile
1. 使用
- 修饰变量
2. 作用
1) 保证可见性
- 对一个 volatile 变量的读,总是能看到(任意线程)对这个 volatile 变量最后的写入
- 遵循 MESI 协议,被 volatile 修饰的变量会不断从主存嗅探 & CAS 无限循环
- 写操作:
- 当写一个
volatile
变量时,JMM 会把该线程对应的本地内存共享变量值刷回主存,其他线程通过嗅探监听,将自己线程该缓存行无效
- 读操作:
- 当读一个
volatile
变量时,本线程若发现本地内存的缓存行无效,会从主内存中读取共享变量
MESI 协议 [缓存一致性协议]
M
[Modify
] 修改- 该缓存行有效,数据只存在于当前线程所在的缓存行
E
[Exclusive
] 独占- 该缓存行有效,数据只存在于当前线程所在缓存行,数据和主存中的数据一致
S
[Shared
] 共享- 该缓存行有效,数据和主存一致,同时存在于多个缓存行
I
[Invalid
] 无效- 该缓存行无效
[注:]
- MESI 只对单个缓存行加锁,而不影响其他缓存行的数据
- 当 Modify 时,只能有一个线程可以进行操作,其他线程若想对该缓存行的数据进行修改操作,会失败,且该缓存行的状态变更为 Invalid
2) 保证有序性,禁止指令重排
① 指令重排
- 普通情况下,源代码会经过以下步骤,并不一定按照原有指令顺序执行
- 即使如此,但仍会遵循
as-if-serial
&happen-before
原则
② volatile 禁止指令重排原理
- 读操作:
- 禁止后面的 普通读写 + volatile 读写 操作重排到它之前
- 借助内存屏障实现
- 写操作:
- 禁止前面的 普通读写 + volatile 读写 操作重排到它之后
- 禁止后面的 volatile 读写 操作重排到它之后
3. JMM
1) 作用
- 屏蔽硬件 & 操作系统的差异,不同系统的主存中有 L1, L2, L3 等分级缓存,JMM 保证在所有系统下 Java 程序能达到一致的访问效果
原子性
- 通过
lock
,unlock
,load
,read
,assign
,use
,store
,write
保证原子操作
有序性
- 借助
synchronized
,volatile
等实现
可见性
- 遵循
happen-before
原则 - 借助
synchronized
,volatile
等实现
4. happen-before
1) 单线程 happen-before 原则
- 在同一个线程中,书写在前面的操作 happen-before 后面的操作
- 前面的操作产生的结果必须对后面的操作可见,而不一定前面的操作必须先于后面的操作执行,如果不影响则可能发生指令重排
2) happen-before 的传递性原则
- 如果 A 操作 happen-before B 操作,B 操作happen-before C 操作,那么 A 操作happen-before C 操作
3) 线程启动的 happen-before 原则
- 同一个线程的
start()
happen-before 此线程的其它方法
4) 线程中断的 happen-before 原则
- 对线程
interrupt()
的调用 happen-before 被中断线程的检测到中断发送的代码 interrupt()
改变的状态必须对后续执行的检测方法可见
5) 线程终结的 happen-before 原则
- 线程中的所有操作都 happen-before 线程的终止检测
6) 对象创建的 happen-before 原则
- 一个对象的初始化完成先于它的
finalize()
调用
7) 锁的 happen-before 原则
- 同一个锁的
unlock()
happen-before 该锁的lock()
8) volatile的 happen-before 原则
- 对一个
volatile
变量的 写 操作 happen-before 对此变量的任意操作 - 对
volatile
变量的写操作的结果对于发生于其后的任何操作的结果都是可见的 - x86 架构下
volatile
通过 内存屏障 和 缓存一致性协议 实现了变量在多核心之间的一致性