• 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.2 多线程下的指令重排序

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

    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,所以会出错。

  • 相关阅读:
    PHP函数include include_once require和require_once的区别
    PHP替换回车换行的三种方法
    PHP获取绝对路径dirname(__FILE__)和__DIR__比较
    jQuery实现倒计时重新发送短信验证码功能示例
    js人民币转大写
    js前端数据验证JS工具
    安卓动画学习笔记
    ActivityNotFoundException: No Activity found to handle Intent
    Android笔记
    再次踩bug:遍历删除list(java.util.ConcurrentModificationException)
  • 原文地址:https://www.cnblogs.com/yifanSJ/p/16314331.html
Copyright © 2020-2023  润新知