1. happens-before原则定义
- 编译器和处理器会对我们程序优化而进行指令重排,但需要保证前一个操作结果对后一个依赖操作可见(其实只是在单线程下能保证),否则就禁止指令重排
1.1 指令重排带来的问题
- 虽然指令重排满足happens-before原则,但这个只能保证单线程下结果具有一致性。
- 在多线程下,指令重排会导致很严重的并发错误,会导致结果不具有一致性
- 多线程下想要结果也满足一致性,则需要加入内存屏障来解决指令重排的问题
2. 内存屏障
2.1 分类
- Load Barrier:在读指令之前插入读屏障,可以让高速缓存中的数据失效,重新从主存加载数据
- Store Barrier:在写指令之后插入写屏障,能让写入缓存的最新数据写回主内存
2.2 详细种类
-
LoadLoad 屏障
- Load1,Loadload,Load2
- 确保Load1所要读入的数据能够在被Load2和后续的load指令访问前读入。
-
StoreStore 屏障
-
Store1,StoreStore,Store2
- 确保Store1的数据在Store2以及后续Store指令操作相关数据之前对其它处理器可见(例如向主存刷新数据)
-
LoadStore 屏障
- Load1; LoadStore; Store2
- 确保Load1的数据在Store2和后续Store指令被刷新之前读取。
-
StoreLoad 屏障
- Store1; StoreLoad; Load2
- 确保Store1的数据在被Load2和后续的Load指令读取之前对其他处理器可见。
3. volatile关键字
3.1 volatile原理
通过内存屏障来防止指令重排,从而实现可见性和有序性
- 在每个volatile写操作的前面插入一个StoreStore屏障。
- 在每个volatile写操作的后面插入一个StoreLoad屏障。
- 在每个volatile读操作的后面插入一个LoadLoad屏障。
- 在每个volatile读操作的后面插入一个LoadStore屏障。
3.2 简要说明
- volatile读前插入读屏障,写后插入写屏障,避免CPU重排导致的问题,实现了多线程之间的数据可见性
4. 内存屏障和缓存一致性
内存屏障会导致CPU缓存刷新,刷新时,会遵循缓存一致性协议
-
lock:解锁时,jvm会强制刷新CPU缓存,导致当前线程更改,对其他线程可见
-
volatile:在标记volatile的字段进行写操作时,会强制刷新CPU缓存,在标记为volatile的字段,每次读取都是直接读内存
-
final:即使编译器在final写操作后,会插入内存屏障,来禁止重排序,保证可见性