天天看点

happens-before原则与内存屏障1. happens-before原则定义2. 内存屏障3. volatile关键字4. 内存屏障和缓存一致性

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写操作后,会插入内存屏障,来禁止重排序,保证可见性