Java并发拾遗(二)——重排序

一、 as-if-serial语义

上篇文章中说道,编译器,运行时的JIT编译器或处理器都会对指令进行重排序以提升程序的执行性能。但这些重排序需要满足as-if-serial语义,不能随便的进行重排序。as-if-serial语义即指:不管怎么重排序,单线程程序的执行结果不能被改变。因此,不能对存在数据依赖关系的操作进行重排序。举个栗子:

1
2
3
double pi = 3.14;
double r = 1.0;
double area = pi * r * r;  

在上面的三个操作中,area变量依赖于 pi 与 r,因此在进行重排序时,pi 与 r 哪个操作先被执行是不确定的,可被重排序的,但是area就不能被重排序,其必须等到pi 与 r的操作完成才能开始执行。这种执行顺序是由编译器、runtime与处理器的重排序机制来保证的,我们可以放心的依赖这种规则。但是,请注意,说这么一大堆的前提是单线程程序,多线程引起之间的顺序错乱是并发引起的,而不是重排序。

上篇文章中,说道Java内置的happens-before规则有一条说:单线程程序,前面的代码 happens-before 后面的代码。但这种说法是说对存在依赖关系的两个操作来讲的,对不存在依赖关系的操作(如两个操作操作不同变量),Java是允许其进行重排序的,因为这可以使程序执行的更快。

二、重排序对多线程的影响

上面所说内容,都是在单线程的前提下进行阐述的,下面来看重排序对多线程的影响。看个栗子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
private class Test {
    int a = 0;
    boolean flag = false;
 
    public void write() {
        a = 1;
        flag = true;
    }
 
    public int read() {
        if(flag) {
            int i = a;
        }
         
        // **
    }
}

如上的代码,flag的初衷是个标记量,用来表示变量a是否被写入了,a写入后才更改flag。然而在多线程的环境下,这种初衷就可能被重排序破坏,从而无法得到保证。这种破坏,提现在两处:

1. 在write()方法内对不存在依赖关系的两条指令重排序

由于在write()方法内的两条操作不存在依赖关系,因而可能被重排序,导致如下图的执行顺序

即标志位flag先被置为TRUE,而a还没被赋值,然后线程B就开始执行了。

2. 对read()方法内存在控制依赖性的语句进行猜测执行导致的重排序

为了提升并行度,加快执行,在实际的执行过程中,编译器或处理器在执行read()方法时可能采取猜测执行的策略来执行。即先把a读进来,然后再判断flag,flag = true时就把预读的a赋值给i,flag = false时就丢弃a。这种对控制依赖性的猜测执行,实际上对read()方法内的语句进行了重排序。

从以上两点,可知在多线程环境下,指令的重排序对于最终的程序结果会产生影响,因而在多线程并发的环境下,需要对重排序进行控制,在一些会影响结果的地方,禁止重排序。

posted @   Mr.do  阅读(329)  评论(0编辑  收藏  举报
编辑推荐:
· 智能桌面机器人:用.NET IoT库控制舵机并多方法播放表情
· Linux glibc自带哈希表的用例及性能测试
· 深入理解 Mybatis 分库分表执行原理
· 如何打造一个高并发系统?
· .NET Core GC压缩(compact_phase)底层原理浅谈
阅读排行:
· 手把手教你在本地部署DeepSeek R1,搭建web-ui ,建议收藏!
· 新年开篇:在本地部署DeepSeek大模型实现联网增强的AI应用
· Janus Pro:DeepSeek 开源革新,多模态 AI 的未来
· 互联网不景气了那就玩玩嵌入式吧,用纯.NET开发并制作一个智能桌面机器人(三):用.NET IoT库
· 【非技术】说说2024年我都干了些啥
点击右上角即可分享
微信分享提示