写点什么

深入理解 Java 内存模型(二)——重排序

  • 2013-01-26
  • 本文字数:2582 字

    阅读完需:约 8 分钟

数据依赖性

如果两个操作访问同一个变量,且这两个操作中有一个为写操作,此时这两个操作之间就存在数据依赖性。数据依赖分下列三种类型:

名称 代码示例 说明 写后读 a = 1;b = a; 写一个变量之后,再读这个位置。 写后写 a = 1;a = 2; 写一个变量之后,再写这个变量。 读后写 a = b;b = 1; 读一个变量之后,再写这个变量。上面三种情况,只要重排序两个操作的执行顺序,程序的执行结果将会被改变。

前面提到过,编译器和处理器可能会对操作做重排序。编译器和处理器在重排序时,会遵守数据依赖性,编译器和处理器不会改变存在数据依赖关系的两个操作的执行顺序。

注意,这里所说的数据依赖性仅针对单个处理器中执行的指令序列和单个线程中执行的操作,不同处理器之间和不同线程之间的数据依赖性不被编译器和处理器考虑。

as-if-serial 语义

as-if-serial 语义的意思指:不管怎么重排序(编译器和处理器为了提高并行度),(单线程)程序的执行结果不能被改变。编译器,runtime 和处理器都必须遵守 as-if-serial 语义。

为了遵守 as-if-serial 语义,编译器和处理器不会对存在数据依赖关系的操作做重排序,因为这种重排序会改变执行结果。但是,如果操作之间不存在数据依赖关系,这些操作可能被编译器和处理器重排序。为了具体说明,请看下面计算圆面积的代码示例:

复制代码
double pi = 3.14; //A
double r = 1.0; //B
double area = pi * r * r; //C

上面三个操作的数据依赖关系如下图所示:

如上图所示,A 和 C 之间存在数据依赖关系,同时 B 和 C 之间也存在数据依赖关系。因此在最终执行的指令序列中,C 不能被重排序到 A 和 B 的前面(C 排到 A 和 B 的前面,程序的结果将会被改变)。但 A 和 B 之间没有数据依赖关系,编译器和处理器可以重排序 A 和 B 之间的执行顺序。下图是该程序的两种执行顺序:

as-if-serial 语义把单线程程序保护了起来,遵守 as-if-serial 语义的编译器,runtime 和处理器共同为编写单线程程序的程序员创建了一个幻觉:单线程程序是按程序的顺序来执行的。as-if-serial 语义使单线程程序员无需担心重排序会干扰他们,也无需担心内存可见性问题。

程序顺序规则

根据 happens- before 的程序顺序规则,上面计算圆的面积的示例代码存在三个 happens- before 关系:

  1. A happens- before B;
  2. B happens- before C;
  3. A happens- before C;

这里的第 3 个 happens- before 关系,是根据 happens- before 的传递性推导出来的。

这里 A happens- before B,但实际执行时 B 却可以排在 A 之前执行(看上面的重排序后的执行顺序)。在第一章提到过,如果 A happens- before B,JMM 并不要求 A 一定要在 B 之前执行。JMM 仅仅要求前一个操作(执行的结果)对后一个操作可见,且前一个操作按顺序排在第二个操作之前。这里操作 A 的执行结果不需要对操作 B 可见;而且重排序操作 A 和操作 B 后的执行结果,与操作 A 和操作 B 按 happens- before 顺序执行的结果一致。在这种情况下,JMM 会认为这种重排序并不非法(not illegal),JMM 允许这种重排序。

在计算机中,软件技术和硬件技术有一个共同的目标:在不改变程序执行结果的前提下,尽可能的开发并行度。编译器和处理器遵从这一目标,从 happens- before 的定义我们可以看出,JMM 同样遵从这一目标。

重排序对多线程的影响

现在让我们来看看,重排序是否会改变多线程程序的执行结果。请看下面的示例代码:

复制代码
class ReorderExample {
int a = 0;
boolean flag = false;
public void writer() {
a = 1; //1
flag = true; //2
}
Public void reader() {
if (flag) { //3
int i = a * a; //4
……
}
}
}

flag 变量是个标记,用来标识变量 a 是否已被写入。这里假设有两个线程 A 和 B,A 首先执行 writer() 方法,随后 B 线程接着执行 reader() 方法。线程 B 在执行操作 4 时,能否看到线程 A 在操作 1 对共享变量 a 的写入?

答案是:不一定能看到。

由于操作 1 和操作 2 没有数据依赖关系,编译器和处理器可以对这两个操作重排序;同样,操作 3 和操作 4 没有数据依赖关系,编译器和处理器也可以对这两个操作重排序。让我们先来看看,当操作 1 和操作 2 重排序时,可能会产生什么效果?请看下面的程序执行时序图:

如上图所示,操作 1 和操作 2 做了重排序。程序执行时,线程 A 首先写标记变量 flag,随后线程 B 读这个变量。由于条件判断为真,线程 B 将读取变量 a。此时,变量 a 还根本没有被线程 A 写入,在这里多线程程序的语义被重排序破坏了!

※注:本文统一用红色的虚箭线表示错误的读操作,用绿色的虚箭线表示正确的读操作。

下面再让我们看看,当操作 3 和操作 4 重排序时会产生什么效果(借助这个重排序,可以顺便说明控制依赖性)。下面是操作 3 和操作 4 重排序后,程序的执行时序图:

在程序中,操作 3 和操作 4 存在控制依赖关系。当代码中存在控制依赖性时,会影响指令序列执行的并行度。为此,编译器和处理器会采用猜测(Speculation)执行来克服控制相关性对并行度的影响。以处理器的猜测执行为例,执行线程 B 的处理器可以提前读取并计算 a*a,然后把计算结果临时保存到一个名为重排序缓冲(reorder buffer ROB)的硬件缓存中。当接下来操作 3 的条件判断为真时,就把该计算结果写入变量 i 中。

从图中我们可以看出,猜测执行实质上对操作 3 和 4 做了重排序。重排序在这里破坏了多线程程序的语义!

在单线程程序中,对存在控制依赖的操作重排序,不会改变执行结果(这也是 as-if-serial 语义允许对存在控制依赖的操作做重排序的原因);但在多线程程序中,对存在控制依赖的操作重排序,可能会改变程序的执行结果。

参考文献

  1. Computer Architecture: A Quantitative Approach, 4th Edition
  2. Concurrent Programming on Windows
  3. Concurrent Programming in Java™: Design Principles and Pattern
  4. JSR-133: Java Memory Model and Thread Specification
  5. JSR 133 (Java Memory Model) FAQ

关于作者

程晓明,Java 软件工程师,国家认证的系统分析师、信息项目管理师。专注于并发编程,就职于富士通南大。个人邮箱: asst2003@163.com


感谢张龙对本文的审校。

给InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家通过新浪微博( @InfoQ )或者腾讯微博( @InfoQ )关注我们,并与我们的编辑和其他读者朋友交流。

2013-01-26 03:1043711

评论

发布
暂无评论
发现更多内容

向云而行华为云桌面成数字办公首选

i生活i科技

论坛回顾|蚂蚁供应链安全建设实践

墨菲安全

软件供应链安全

百度工程师带你体验引擎中的nodejs

百度Geek说

JavaScript 前端 nodejs 12 月 PK 榜

安全且高效!华为云会议全方位提升开会体验

路过的憨憨

集结多种便捷功能,华为云会议大幅提升开会体验

路过的憨憨

远程灵活办公,华为云桌面了解一下

与时俱进的时代

学习大数据技术后的就业前景怎么样

小谷哥

【基础知识】PCB板上的字母数字是什么意思,代表哪些元器件?

华秋PCB

元器件 PCB PCB设计

会议云上开,华为云会议具有超高性价比

路过的憨憨

云上智慧化办公,华为云桌面成为首选!

与时俱进的时代

教你用JavaScript实现大转盘

小院里的霍大侠

JavaScript 初学者 入门实战

华为云CDN助力企业用户体验全面优化,让企业“惠”加速

爱尚科技

阿里云助力抖音,为2022世界杯护航

云布道师

阿里云

在华为云桌面Workspace上,启泰智能工业设计效率翻倍

i生活i科技

【FAQ】在华为鸿蒙车机上集成华为帐号的常见问题总结

HarmonyOS SDK

HMS Core

神州云科打出“组合拳”,双轨超高可用架构引领信创高质量发展

通明湖

不会PS怎么办?教你3种方法一键更换证件照背景色

互联网民工阿强

word ps 人像抠图 背景替换 证件照

数据价值深度挖掘,分析服务上线“探索”能力

HarmonyOS SDK

HMS Core

【JVM实战系列】「监控调优体系」实战开发arthas-spring-boot-starter监控你的微服务是否健康!

码界西柚

Java JVM Alibaba Arthas 12 月 PK 榜

RabbitMQ、RocketMQ、Kafka延迟队列实现

艾小仙

Java kafka RocketMQ RabbitMQ 延迟队列

集结多种便捷功能,华为云会议让沟通简单化

路过的憨憨

用华为云桌面有多爽?问问设计师就知道了!

与时俱进的时代

QCN9074 802.11ax 4x4 MU-MIMO 6GHz wifi6E//qcn9072 qcn9024 qcn64 wallys

wallysmeng

QCN9074 QCN9024 QCN9072 qcn9064

女生参加前端培训学习有前途吗?

小谷哥

武汉java培训后可以选择那些就业方向

小谷哥

四大领先优势加持,华为云会议服务更省心可靠!

路过的憨憨

DeFi模式NFT游戏开发技术

薇電13242772558

NFT链游

线上线下大数据培训机构应该怎么选

小谷哥

schema设计与管理

刺猬

双十二优惠,华为云桌面喊你来采购

与时俱进的时代

连续7年领跑!在华为云桌面,藏了一盘数字办公的大棋

i生活i科技

深入理解Java内存模型(二)——重排序_Java_程晓明_InfoQ精选文章