详细解析Java内存,处理器,编译器重排序以及它对线程的影响

    欢迎大家搜索“小猴子的技术笔记”关注我的公众号,有问题可以及时和我交流。


    我们在编写程序的时候有一个编写代码的顺序,那么计算机执行的时候就是按照我们编写代码的顺序来执行的吗?答案是:不一定。如果两个代码之间没有依赖关系的话,那么编译器和处理器常常会对我们的编码指令重排序。重排序是指编译器和处理器为了优化程序性能而对指令序列进行重新排序的一种手段,我们编写一个Java代码从源代码到最后的执行顺序如下:
在这里插入图片描述
    源代码:也就是我们用开发工具写的代码。

    编译器优化重排序:编译器在不改变单线程程序语义的前提下,可以重新安排语句的执行顺序。

    指令级并行重排序:现代处理器采用了指令级并行技术来将多条指令重叠执行。如果数据不存在依赖,处理器就可以改变语句对应机器指令的执行顺序。

    内存系统重排序:当代处理器使用写缓冲区来临时保存向内存写入的数据,这使得加载和存储操作看上去可能是在乱序执行。我们来看下面这个例子:

    假设有处理器A和处理器B两个处理器,a和b的初始化状态为0 。在处理器A中执行下面代码(均为伪代码):

a=1;
x=b;

    在处理器B中执行下面代码:


b=2;
y=a;

    处理器允许执行后得到的结果是x=y=0。来看一下处理器和内存的交互图:
在这里插入图片描述
    因为现代处理器都会使用写缓存,因此现在处理器都会允许对写-读的操作进行重排序。

    写缓冲区的作用:因为处理器和内存的处理速度不是一个量级的,因此避免由于处理器停顿下来向内存写入数据而产生延迟,所以每个处理器都有一个仅仅对自己处理器可见的写缓冲区。现代处理器会通过批处理的方式刷新写缓冲区,以及合并写缓冲区中对同一个内存地址的多次写,减少对数据总线的调用。

    介绍完了重排序之后,我们需要知道在单核处理器中,如果两个变量存在了数据依赖,编译器和处理器是不会改变存在数据依赖关系的两个操作的执行顺序的。那么重排序对多线程有什么影响呢?来看看下面的这个例子:


public class ReorderExample {
    int a = 0;
    boolean flag = false;
    public void writer() {
        // 操作1
        a = 1;
        // 操作2
        flag = true;
    }
    public void reader() {
        // 操作3
        if (flag) {
            // 操作4
            int i = a * a;
            System.out.println(i);
        }
    }
}

    如果A线程先执行“writer()”方法,B线程接着执行“reader()”方法,那么线程B在执行的时候能否看到线程A对共享变量a的写入呢?

    答案是不一定能看到,因为操作1和操作2没有数据依赖关系,所以编译器和处理器可以对这两个操作进行重排序。假定操作1和操作2进行了重排序,那么线程B在执行的时候得到的结果就有可能是i=0。

    在操作3和操作4先进行了一个判断在计算,它们之间存在控制依赖关系。当代码中存在控制依赖性时,会影响指令序列执行的并行度。线程B处理器可以提前读取并计算“a*a”,然后把计算结果临时保存到一个名为重排序缓存(Reorder Buffer,ROB)的硬件缓存中。当操作3的条件判断为真的时候,就把该计算结果写入到变量i中。
在这里插入图片描述
    由此可以明白,如果多线程的话,重排序是会影响多线程的执行结果的
在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值