JVM的四种内存屏障

1、为什么要有内存屏障

为了解决cpu,高速缓存,主内存带来的的指令之间的可见性和重序性问题

我们都知道计算机运算任务需要CPU和内存相互配合共同完成,其中CPU负责逻辑计算,内存负责数据存储。CPU要与内存进行交互,如读取运算数据、存储运算结果等。由于内存和CPU的计算速度有几个数量级的差距,为了提高CPU的利用率,现代处理器结构都加入了一层读写速度尽可能接近CPU运算速度的高速缓存来作为内存与CPU之间的缓冲:将运算需要使用的数据复制到缓存中,让CPU运算可以快速进行,计算结束后再将计算结果从缓存同步到主内存中,这样处理器就无须等待缓慢的内存读写了。就像下面这样:
在这里插入图片描述

每个CPU都会有自己的缓存(有的甚至L1,L2,L3),缓存的目的就是为了提高性能,避免每次都要向内存取但是这样的弊端也很明显:不能实时的和内存发生信息交换,会使得不同CPU执行的不同线程对同一个变量的缓存值不同。用volatile关键字修饰变量可以解决上述问题,那么volatile是如何做到这一点的呢?那就是内存屏障,内存屏障是硬件层的概念,不同的硬件平台实现内存屏障的手段并不是一样,java通过屏蔽这些差异,统一由jvm来生成内存屏障的指令

volatile的有序性和可见性
volatile的内存屏障策略非常严格保守,非常悲观且毫无安全感的心态:在每个volatile写操作前插入StoreStore屏障,在写操作后插入StoreLoad屏障;在每个volatile读操作前插入LoadLoad屏障,在读操作后插入LoadStore屏障;由于内存屏障的作用,避免了volatile变量和其它指令重排序、实现了线程之间通信,使得volatile表现出了锁的特性。


重排序:代码的执行顺序不按照书写的顺序,为了提升运行效率,在不影响结果的前提下,打乱代码运行
int a=1;
int b=2;
int c=a+b;
int c=5;
这里的int c=5这个赋值操作可能发生在int a=1这个操作之前

2、硬件上面的内存屏障

  • Load屏障,是x86上的”ifence“指令,在其他指令前插入ifence指令,可以让高速缓存中的数据失效,强制当前线程从主内存里面加载数据
  • Store屏障,是x86的”sfence“指令,在其他指令后插入sfence指令,能让当前线程写入高速缓存中的最新数据,写入主内存,让其他线程可见

3、Java里面的四种内存屏障

  • LoadLoad屏障:举例语句是Load1; LoadLoad; Load2(这句里面的LoadLoad里面的第一个Load对应Load1加载代码,然后LoadLoad里面的第二个Load对应Load2加载代码),此时的意思就是,在Load2及后续读取操作从内存读取数据到CPU前,保证Load1从主内存里要读取的数据读取完毕。

  • StoreStore屏障:举例语句是 Store1; StoreStore; Store2(这句里面的StoreStore里面的第一个Store对应Store1存储代码,然后StoreStore里面的第二个Store对应Store2存储代码)。此时的意思就是在Store2及后续写入操作执行前,保证Store1的写入操作已经把数据写入到主内存里面,确认Store1的写入操作对其它处理器可见。

  • LoadStore屏障:举例语句是 Load1; LoadStore; Store2(这句里面的LoadStore里面的Load对应Load1加载代码,然后LoadStore里面的Store对应Store2存储代码),此时的意思就是在Store2及后续代码写入操作执行前,保证Load1从主内存里要读取的数据读取完毕。

  • StoreLoad屏障:举例语句是Store1; StoreLoad; Load2(这句里面的StoreLoad里面的Store对应Store1存储代码,然后StoreLoad里面的Load对应Load2加载代码),在Load2及后续读取操作从内存读取数据到CPU前,保证Store1的写入操作已经把数据写入到主内存里,确认Store1的写入操作对其它处理器可见。

4、使用内存屏障保存Volatile的有序性

volatile关键字是通过内存屏障,禁止被它修饰的变量发生指令重排操作

4.1 单线程下的指令重排序

处理器为了提高程序运行效率,可能会对输入代码进行优化,使得程序中各个语句的执行顺序同代码中的顺序不一致,但是它会保证程序最终执行结果和代码顺序执行的结果是一致的。比如:

int i=10;//语句1
int j=10;//语句2

在执行时,有可能对代码进行重排序,比如先执行语句2,再执行语句1。但是如果代码,编程下边这样:

int i=0;
int j=0;
j++;//语句3
i=j+1;//语句4

这时,语句3和语句4并不会进行重排序。因为语句3和4之间有依赖关系,重排序后会影响结果。

4.1 多线程下的指令重排序

以上说的是单线程的情况,期望结果等于输出结果。下面看多线程的情况,如下代码:

boolean flag=false;
private Context context;
//线程1
context=loadContext();//语句1
flag=true;//语句2

//线程2
if(flag){
	dowork(context);
}

如果线程1执行的时候,语句1和语句2进行了重排序,先执行语句2,在还没有执行语句1时,这时线程2 将要执行if,那么就会进入到if语句块中,而context还是null,所以会出错。

参考:https://www.zhihu.com/question/409020190/answer/1360584418
参考:https://www.itqiankun.com/article/1564577564

  • 2
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

还能坚持

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值