写点什么

as-if-serial 规则和 happens-before 规则的区别

作者:Java高工P7
  • 2021 年 11 月 10 日
  • 本文字数:1885 字

    阅读完需:约 6 分钟

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 规则




上面的内容讲述了重排序原则,一会是编译器重排序一会是处理器重排序,如果让程序员再去了解这些底层的实现以及具体规则,那么程序员的负担就太重了,严重影响了并发编程的效率。因此,JMM 为程序员在上层提供了六条规则,这样我们就可以根据规则去推论跨线程的内存可见性问题,而不用再去理解底层重排序的规则。

happens-before 定义

happens-before 的概念最初由 Leslie Lamport 在其一篇影响深远的论文(《Time,Clocks and the Ordering of Events in a Distributed System》)中提出,有兴趣的可以 google 一下。JSR-133 使用 happens-before 的概念来指定两个操作之间的执行顺序。由于这两个操作可以在一个线程之内,也可以是在不同线程之间。因此,**JMM 可以通过 happens-before 关系向程序


【一线大厂Java面试题解析+后端开发学习笔记+最新架构讲解视频+实战项目源码讲义】
浏览器打开:qq.cn.hn/FTf 免费领取
复制代码


员提供跨线程的内存可见性保证**(如果 A 线程的写操作 a 与 B 线程的读操作 b 之间存在 happens-before 关系,尽管 a 操作和 b 操作在不同的线程中执行,但 JMM 向程序员保证 a 操作将对 b 操作可见)。具体的定义为:


1)如果一个操作 happens-before 另一个操作,那么第一个操作的执行结果将对第二个操作可见,而且第一个操作的执行顺序排在第二个操作之前。


2)两个操作之间存在 happens-before 关系,并不意味着 Java 平台的具体实现必须要按照 happens-before 关系指定的顺序来执行。如果重排序之后的执行结果,与按 happens-before 关系来执行的结果一致,那么这种重排序并不非法(也就是说,JMM 允许这种重排序)。


上面的 1)是 JMM 对程序员的承诺


从程序员的角度来说,可以这样理解 happens-before 关系:如果 A happens-before B,那么 Java 内存模型将向程序员保证——A 操作的结果将对 B 可见,且 A 的执行顺序排在 B 之前。注意,这只是 Java 内存模型向程序员做出的保证!


上面的 2)是 JMM 对编译器和处理器重排序的约束原则


正如前面所言,JMM 其实是在遵循一个基本原则:只要不改变程序的执行结果(指的是单线程程序和正确同步的多线程程序),编译器和处理器怎么优化都行。JMM 这么做的原因是:程序员对于这两个操作是否真的被重排序并不关心,程序员关心的是程序执行时的语义不能被改变(即执行结果不能被改变)。因此,happens-before 关系本质上和 as-if-serial 语义是一回事。

具体规则

具体的一共有六项规则:


  1. 程序顺序规则:一个线程中的每个操作,happens-before 于该线程中的任意后续操作。

  2. 监视器锁规则:对一个锁的解锁,happens-before 于随后对这个锁的加锁。

  3. volatile 变量规则:对一个 volatile 域的写,happens-before 于任意后续对这个 volatile 域的读。

  4. 传递性:如果 A happens-before B,且 B happens-before C,那么 A happens-before C。

  5. start()规则:如果线程 A 执行操作 ThreadB.start()(启动线程 B),那么 A 线程的 ThreadB.start()操作 happens-before 于线程 B 中的任意操作。

  6. join()规则:如果线程 A 执行操作 ThreadB.join()并成功返回,那么线程 B 中的任意操作 happens-before 于线程 A 从 ThreadB.join()操作成功返回。

  7. 程序中断规则:对线程 interrupted()方法的调用先行于被中断线程的代码检测到中断时间的发生。

  8. 对象 finalize 规则:一个对象的初始化完成(构造函数执行结束)先行于发生它的 finalize()方法的开始。


下面以一个具体的例子来讲下如何使用这些规则进行推论:


double pi = 3.14; //A


double r = 1.0; //B


double area = pi * r * r; //C

用户头像

Java高工P7

关注

还未添加个人签名 2021.11.08 加入

还未添加个人简介

评论

发布
暂无评论
as-if-serial规则和happens-before规则的区别