volatile与内存屏障

volatile的作用

1.保证变量的可见性
2.局部限制指令重排(指令重排存在多种情况:1.编译器重排;2.处理器并行重排;3.因为store buffer,invalid queue等异步机制的存在导致的内存重排)

为了达到这两个目的,volatile做了两件事情:
1.禁止编译器的优化和重排
2.通过内存屏障限制处理器重排

首先明确下排序规则,简单总结如下(参考JSR-133)
a.第二个操作为volatile写,则无论第一个操作是什么,都无法重排序;
b.第一个操作为volatile读,则无论第二个操作是什么,都无法重排序;
c.第一个操作为volatile写,第二个操作为volatile读,无法重排序;

如何限制编译器指令重排

规则c保证了volatile变量自身的可见性,abc一起保证了操作的局部有序以及一些跨线程数据依赖的正确性。在某些情况下,不仅仅要求volatile变量本身的可见性,也需要有序性来保证语义的正确,如下面的典型场景:

两个变量:

int x=0;
int y=0;

线程A执行:

y=1     //1
x=1     //2

线程B执行:

while(x==1) {      //3
    assert y==1;   //4
}

按照程序的语义,最后的断言应该是通过,但实际情况并非如此。原因有两个:1.如果没有a限制,操作1和操作2之前可能会被重排序,操作3的条件成立时,操作1可能还未执行,导致断言失败;2.编译器会对线程B的代码做优化,因为编译器无法判断跨线程的数据依赖,单独从线程B的视角看,x是不会变化的,为了提升性能,while(x==1)为直接被替换为while(false),永远无法执行操作4。

解决方案就是将变量x声明为volatile,限制的编译器的优化和操作1、操作2的重排,同时保证操作2的结果对操作3立即可见。

通过内存屏障限制处理器重排

四类内存屏障:LoadLoad、StoreStore、StoreLoad、LoadStore
volatile的内存屏障策略:
volatile写之前插入StoreStore屏障;(规则a,防止重排)
volatile写之后插入StoreLoad屏障;(规则c,保障可见性)
volatile读之后插入LoadStore屏障;(规则b,防止重排)
volatile读之后插入LoadLoad屏障;(规则b,防止重排)

再补充一些相关知识点,可能比较混乱,但个人觉得有助于对内存屏障的理解
1.StoreLoad的主要目的是保证volatile变量自身写后读的可见性,在实现上开销最大。
2.内存屏障作为一种逻辑抽象,具体实现有多种,比如x86下有mfence和lock指令,JVM选择了lock,及在写volatile变量时锁缓存总线,并且让其他CPU上对应缓存行的数据失效。
3.x86下只有StoreLoad屏障是有效操作,其他的屏障均为no-op。主要原因有二:1. x86中store-buffer是一个FIFO队列,结构上保证了写入顺序;2. x86中没有invalid queue,因此不需要LoadLoad或LoadStore来强制消费失效队列。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值