volatial探讨(一)-重排序和内存屏障

Volatile探讨之重排序

1.1.1 重排序问题引入

什么是内存屏障?内存屏障主要解决了哪些问题?这些问题解释起来比较复杂。下面我们来解释,先看一个示例:

示例-1

首先来验证一下代码是否是是循序执行的

package com.gxw.first.code.volite;

/**
 *  验证代码是否顺序执行
 * 
 */
public class ReSort {
    private static int a = 0, b = 0;
    private static int x = 0, y = 0;
//    private volatile static int a = 0, b = 0;  // 1-1注释
//    private volatile static int x = 0, y = 0;  // 1-2注释
    
    public static void main(String []  args) throws InterruptedException {
        long start=System.currentTimeMillis();
        int i = 0;
        System.out.println("Process action !");
        for (;;){
            i++;
            a = 0; b = 0; x = 0; y = 0;
            Thread t1 = new Thread(
                    () -> {
                        a = 1;
                        x = b;
                    }
            );
            Thread t2 = new Thread(
                    () -> {
                        b = 1;
                        y = a;
                    }
            );

            t1.start();t2.start();
            t1.join();t2.join();
            /**
             * 在顺序执行的情况下如下如下代码永远不可能打印
             */
            if (x == 0 && y == 0) {
                System.out.println("X=0 , Y=0 执行了 " + i + "次");
                break;
            }else {
                //空
            }
        }
        System.out.println("执行了"+(System.currentTimeMillis()-start)/1000+"秒");

    }
}

结论:

代码不是顺序执行的

描述:

假设代码总是顺序执行的,所以上示例中,对于任意的t1,t2的随机中断,都不存在x=0的同时y=0
(在顺序执行的情况下,x=0时a=1是必然的,同理y=0时b=1是必然的,即使在没有线程安全的情况下)

执行结果:

>Process action !
>X=0 , Y=0 执行了 40937>执行了14> 
>Process finished with exit code 0

1.1.1.2 volatile修饰下的执行结果

我们打开代码中1-1和1-2的注释
看一下执行结果

>Process action !
>程序一直不退出

结论:

代码在无volatile修饰的情况下并非是顺序执行的!
代码在无volatile修饰的情况下并非是顺序执行的
代码在无volatile修饰的情况下并非是顺序执行的
重要的事情多说几遍

这个结论是不是听起来很刺激?代码不是顺序执行的?难道之前写的代码能正常跑是闹鬼了?

1.1.2 重排序cpu层的探讨

首先我们要破除封建迷信,这里引入一个概念

as-if-serial:看起来是顺序执行的(也叫最终一致性

1.1.2.1 问提在哪?

计算机组成原理中写过:
cpu组成原理的教科书中写过,cpu组成的三个核心部分

1、寄存器(IR)
2、运算器(ALU)
3、控制器(Control)
寄存器也算控制器的一部分

在这里插入图片描述
cpu的三个单元

1、缓存单元(cache)
2、运算单元(ALU)
3、控制单元(Control)

版权归属,转载请署名
Instruction Register:指令寄存器
ALU:寄存器基础运算单元,负责执行命令
Registry:寄存器里面装的是从内存读到的指令数据
PC:指令的指针

1、寄存器负责从各个线程中读取要执行的指令
2、读取指令后,寄存器讲把指令交给运算器执行
3、由程序计数器来记录执行到了哪个指令
4、在发生线程切换的时候,其他的控制单元会把pc和寄存器中的内容扔进Cache区,以便线程唤醒的时候重新加载(这个过程也叫上下文切换)

其中运算器(ALU)的执行是非常快的、远远大于寄存器和控制器

1.1.2.2 硬件层解决方案-超线程

为了解决ALU执行速度远远大于寄存器和控制器的问题,各个cpu厂家也是绞尽脑汁。提出了线程撕裂者,超线程之类的技术。让现代的cpu一个核心可以支持多个线程的同时执行。其实原理很简单。
在这里插入图片描述
我想看到上图大家就理解了,硬件层的解决方法相当暴力,既然寄存器和运算器的执行速度不匹配,那么就多加几个寄存器来解决问题。

1.1.2.3 软件层解决方案-重排序

重排序
通俗的讲:
由于cpu的运算单元执行时很快,而寄存器从内存中读取指令速度是很慢。所以在这个时候如果声明了很多变量,即从内存中读取很多值到寄存器中,会造成运算单元的空闲浪费了很多算力。

我们先看一下如下代码

int i=0; //定义一个变量i
int j=12345; //定义一个变量j
i++; //执行i的自增

    这个时候为了提升计算单元的利用率,所以寄存器会选择异步执行从内存中读取数据的操作。

1、异步执行了从内存中读取数据到寄存器的过程
2、查询后面有没有可以利用计算单元(ALU)的代码
3、如果有则判断执行改代码是否影响最终一致性原则(as-if-serial)。
4、不影响则执行运算,影响则等待回调

如上代码:在单线程的情况下,i++和j=12345两句交换了顺序也并不影响代码的最终结果。分析如下步骤
    
    

寄存器执行j=12345的时候,从内存取值,造成运算单元空闲。

1、寄存器异步取出 12345
2、为了提升运算单元的利用率,判断后面并没有对 变量j 的运算(即运行i++不会影响程序的最终结果)
3、把i++交给运算器(ALU)执行
4、运算单元由于执行比较快,所以i++先执行完毕。
5、此时变量j从内存中加载j=12345成功
6、所以宏观上就就造成的i++先执行,j=12345后执行的策略

这种过程称为重排序
    
    

总结重排序的原因:

1、重排序的原则是 as-if-serial 即不影响最终一致性
2、重排序的原因是
在cpu运行过程中 运算远比从内存读取值得速度要快的多。

到这里是不是有点明白程序为什么没有顺序执行。

怎么防止这种情况发生呢?

1.1.3内存屏障解释

为了可以人工干预重排序的发生,所以各个cpu都提供了内存屏障的功能

1.1.3.1 怎么防止重排序

版权归属转载署名

版权归属转载署名
我想到这里各位就明白内存屏障是个啥东西了
简单来说,就是为了防止重排序,所以在变量使用的前后加了个墙,防止其他指令插队。

大致来说内存屏障有这么几种

1.1.3.2 内存屏障的种类
内存屏障伪代码说明
LoadLoad BarrierLoad; barrier; load在A指令执行load的时候,B指令的load不能插队
StoreStore Barrierstore ;barrier; store在A指令执行写的时候,B指令的写操作不能插队。刷新缓存
LoadStore Barrierload; barrier;store在A指令执行读的时候,B指令的写不能插队
StoreLoad Barrierstore:load;barrier在A指令执行写的时候,B指令的读不能插队。刷新缓存

volatitle写操作加的内存屏障

------store-store-barrier--------
volatile-写
------store-Load-barrier--------

注释:为了保证不受重排序的影响所有的写屏障被触发后都当前cpu都会把写后的值,回写到内存中,同时给其他cpu发出一个该变量的值已经改变的信令。

volatitle读操作加的内存屏障
----load-load-barrier----
volatitle-读
----load-store-barrier—

由此volatitle中的两个问题都已经可以充分的解释了
1、volatile的可见性,是由于写屏障触发了缓存的刷新。
2、volatile修饰的变量,可以用来防止重排序的发生。

后续会更行内存屏障唤醒其他cpu刷新缓存的过程,以及volatitle刷新内存带来的问题,和一个juc包中解决这个问题的经典案例(内存对齐)

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值