被volatile修饰的变量的特点
- 可见性
- 有序性(禁止重排序)
注意:没有原子性
volatile的内存语义
当写一个volatile变量时,JMM会把该线程对应的本地内存中的共享变量值立即刷新回主内存中。
当读一个volatile变量时,JMM会把该线程对应的本地内存设置无效,直接从主内存中读取共享变量。
volatile写直接刷新主内存,读直接从主内存中读取。
volatile如何保证有序性
什么是happens-befero先行原则
在JMM中如果一个操作执行的结果需要对另一个操作可见性或者代码重排序,那么这两个操作之间必须存在happens-before关系。
注意:
happens-before之8条规则
- 次序规则
一个线程内,按照代码顺序,写在前面的操作先行发生于写在后面的操作。
- 锁定规则
unlock必须执行于lock之后。
- volatile变量规则
对一个volatile变量的写操作先行发生于这个变量读之前。
- 传递规则
操作A先行发生于操作B,操作B先行发生于操作C,所以操作A先行发生于操作C。
- 线程启动规则
Thread对象的start()方法先行发生于此线程的每一个动作。
- 线程中断规则
对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生。
- 线程终止规则
线程中的所有操作都先行发生于对此线程的终止检测。
- 对象终结规则
一个对象的初始化完成先行发生于它的finalize()方法的开始。
内存屏障是什么
对happens-before原则的落地实现。
JVM中提供了四类内存屏障指令
-
loadload
-
storestore
-
loadstore
-
storeload
Unsafe.class -> Unsafe.java -> Unsafe.cpp -> OrderAccess.hpp
JMM将内存屏障插入策略分为4种
- 写 在每一个volatile写操作的前面插入一个StoreStore屏障。
在每一个volatile写操作的后面插入一个Storeload屏障。
- 读 在每一次volatile读操作的后面插入一个LoadLoad屏障。
在每一次volatile读操作的后面插入一个LoadStore屏障。
如何正确使用volatile
- 单一赋值
- 状态标志,判断业务是否结束
- DCL双端锁的发布