1.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关系向程序员提供跨线程的内存可见性保证(如果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语义是一回事。

下面来比较一下as-if-serial和happens-before:

as-if-serial VS happens-before

1.  as-if-serial语义保证单线程内程序的执行结果不被改变,happens-before关系保证正确同步的多线程程序的执行结果不被改变。
2.  as-if-serial语义给编写单线程程序的程序员创造了一个幻境:单线程程序是按程序的顺序来执行的。happens-before关系给编写正确同步的多线程程序的程序员创造了一个幻境:正确同步的多线程程序是按happens-before指定的顺序来执行的。
3.  as-if-serial语义和happens-before这么做的目的,都是为了在不改变程序执行结果的前提下,尽可能地提高程序执行的并行度。

具体规则 :

具体的一共有六项规则:

  • 程序顺序规则:一个线程中的每个操作,happens-before于该线程中的任意后续操作。
  • 监视器锁规则:对一个锁的解锁,happens-before于随后对这个锁的加锁。
  • volatile变量规则:对一个volatile域的写,happens-before于任意后续对这个volatile域的读。
    传递性:如果A happens-before B,且B happens-before C,那么A happens-before C。
  • start()规则:如果线程A执行操作ThreadB.start()(启动线程B),那么A线程的ThreadB.start()操作happens-before于线程B中的任意操作。
  • join()规则:如果线程A执行操作ThreadB.join()并成功返回,那么线程B中的任意操作happens-before于线程A从ThreadB.join()操作成功返回。
  • 程序中断规则:对线程interrupted()方法的调用先行于被中断线程的代码检测到中断时间的发生。
    对象finalize规则:一个对象的初始化完成(构造函数执行结束)先行于发生它的finalize()方法的开始。

总结

上面已经聊了关于JMM的两个方面:1. JMM的抽象结构(主内存和线程工作内存);2. 重排序以及happens-before规则。接下来,我们来做一个总结。从两个方面进行考虑。1. 如果让我们设计JMM应该从哪些方面考虑,也就是说JMM承担哪些功能;2. happens-before与JMM的关系;3. 由于JMM,多线程情况下可能会出现哪些问题?

JMM的设计


JMM层级图

JMM是语言级的内存模型,在我的理解中JMM处于中间层,包含了两个方面:(1)内存模型;(2)重排序以及happens-before规则。同时,为了禁止特定类型的重排序会对编译器和处理器指令序列加以控制。而上层会有基于JMM的关键字和J.U.C包下的一些具体类用来方便程序员能够迅速高效率的进行并发编程。站在JMM设计者的角度,在设计JMM时需要考虑两个关键因素:

程序员对内存模型的使用 程序员希望内存模型易于理解、易于编程。程序员希望基于一个强内存模型来编写代码。
编译器和处理器对内存模型的实现 编译器和处理器希望内存模型对它们的束缚越少越好,这样它们就可以做尽可能多的优化来提高性能。编译器和处理器希望实现一个弱内存模型。
另外还要一个特别有意思的事情就是关于重排序问题,更简单的说,重排序可以分为两类:

会改变程序执行结果的重排序。
不会改变程序执行结果的重排序。
JMM对这两种不同性质的重排序,采取了不同的策略,如下。

对于会改变程序执行结果的重排序,JMM要求编译器和处理器必须禁止这种重排序。
对于不会改变程序执行结果的重排序,JMM对编译器和处理器不做要求(JMM允许这种 重排序)
JMM的设计图为:

JMM设计示意图 从图可以看出:

JMM向程序员提供的happens-before规则能满足程序员的需求。JMM的happens-before规则不但简单易懂,而且也向程序员提供了足够强的内存可见性保证(有些内存可见性保证其实并不一定真实存在,比如上面的A happens-before B)。
JMM对编译器和处理器的束缚已经尽可能少。从上面的分析可以看出,JMM其实是在遵循一个基本原则:只要不改变程序的执行结果(指的是单线程程序和正确同步的多线程程序),编译器和处理器怎么优化都行。例如,如果编译器经过细致的分析后,认定一个锁只会被单个线程访问,那么这个锁可以被消除。再如,如果编译器经过细致的分析后,认定一个volatile变量只会被单个线程访问,那么编译器可以把这个volatile变量当作一个普通变量来对待。这些优化既不会改变程序的执行结果,又能提高程序的执行效率。

happens-before与JMM的关系

happens-before与JMM的关系

一个happens-before规则对应于一个或多个编译器和处理器重排序规则。对于Java程序员来说,happens-before规则简单易懂,它避免Java程序员为了理解JMM提供的内存可见性保证而去学习复杂的重排序规则以及这些规则的具体实现方法


[1] : https://github.com/CL0610/Java-concurrency/tree/master/3.java%E5%86%85%E5%AD%98%E6%A8%A1%E5%9E%8B%E4%BB%A5%E5%8F%8Ahappens-before%E8%A7%84%E5%88%99

happens-before 规则相关推荐

  1. 浅显易懂 Makefile 入门 (03)— 目标文件搜索(VPATH 和 vpath 的区别和使用)、隐含规则

    1. 目标文件搜索(VPATH和vpath) 如果需要的文件是存在于不同的路径下(即源文件与 Makefile 文件不在同一个路径下),在编译的时候就用到了 Makefile 中为我们提供的目录搜索文 ...

  2. 浅显易懂 Makefile 入门 (01)— 什么是Makefile、为什么要用Makefile、Makefile规则、Makefile流程如何实现增量编译

    1. 什么是 Makefile Makefile 文件描述了 Linux 系统下 C/C++ 工程的编译规则,它用来自动化编译 C/C++ 项目.一旦写编写好 Makefile 文件,只需要一个 ma ...

  3. Java中类、常量、变量、方法名等命名规则

    Java中类.常量.变量.方法名等命名规则 命名规则: 命名由字母.数字.下划线.美元符号($)构成. 命名不可以数字作为开头. 长度无限制,但不可存在空格. 大小写所表述内容含义不同. 命名不可与J ...

  4. LeetCode简单题之统计匹配检索规则的物品数量

    题目 给你一个数组 items ,其中 items[i] = [typei, colori, namei] ,描述第 i 件物品的类型.颜色以及名称. 另给你一条由两个字符串 ruleKey 和 ru ...

  5. LLVM一些语法规则

    LLVM一些语法规则 LLVM文档 LLVM编译器基础架构支持广泛的项目,从工业强度编译器到专门的JIT应用程序,再到小型研究项目. 同样,文档分为几个针对不同受众的高级别分组: LLVM设计概述 几 ...

  6. Go 学习笔记(53)— Go 标准库之 path(判断绝对路径、拆分目录和文件、组合路径、返回路径目录、获取路径最后文件名、获取文件扩展名、路径匹配规则)

    1. 概述说明 import "path" path 实现了对斜杠分隔的路径的实用操作函数. 2. 主要函数 2.1 func IsAbs func IsAbs(path stri ...

  7. Go 学习笔记(26)— Go 习惯用法(多值赋值,短变量声明和赋值,简写模式、多值返回函数、comma,ok 表达式、传值规则)

    1. 多值赋值 可以一次性声明多个变量,并可以在声明时赋值,而且可以省略类型,但必须遵守一定的规则要求. package main import "fmt"func main() ...

  8. [笔记]C#基础入门(八)——C#标识符的命名规则

    程序中的变量名.常量名.类名.方法名,都叫做标识符.C#有一套标识符的命名规则,如果命名时不遵守规则,就会出错.这套规则简单说有下面三条: ①标识符只能由英文字母.数字和下划线组成,不能包含空格和其他 ...

  9. 关于对IPtables规则的理解

    1,iptables中的所有规则都会存放到/etc/sysconfig/iptables文件中 2,最前端的规则既是现有规则 3,如果要改变现有的规则有两种办法: (1) 使用iptables -t ...

  10. [摘录]代码优化规则

    <<代码优化:有效使用内存>>代码优化建议:     1. 展开读取内存的循环     2. 消除数据相关性         如果请求的RAM单元存在地址数据相关性(也就是说, ...

最新文章

  1. 大场景三维点云的语义分割综述
  2. boost::ratio_greater_equal相关的测试程序
  3. java a[i].setx(-1);_java – setX和setY在尝试定位图像时不起作用
  4. 2021年春季学期期末统一考试 西方经济学(本) 试题
  5. SI4432射频芯片方案物联网无线通信模块数传的典型应用
  6. Celery介绍及常见错误
  7. button的onclick函数一直刷新
  8. Oracle数据库空间突然增大,Oracle数据库突然宕机,处理方案
  9. JSF是什么?它与Struts是什么关系?
  10. python 图形_Python切分图像小案例(1、3、2、4象限子图互换)
  11. [转载] python中chr()和ord()函数的用法
  12. php 事务回滚,php实现事务回滚的方法
  13. EasyUI 1.5.1 美化主题大包 Insdep Theme 1.0.3 正式版已发布,开源下载
  14. 把大写数字转换成阿拉伯数字后排序
  15. 拼手气红包算法_线段切割法
  16. 围城---一段不错的观后感
  17. 2019联想创新科技大会:“端边云网智”一切就绪
  18. excel高级筛选怎么用_如何用excel在筛选状态下怎么复制粘贴?
  19. HTML筑基知识点二
  20. Bmob用户功能详解(二)

热门文章

  1. 笔记:离散时间形式的索洛模型
  2. python BeautifulSoup爬取豆瓣电影top250信息并写入Excel表格
  3. LoRaWan规范介绍
  4. 怎么批量给文件名编号?
  5. 在docker中编译tor 源码
  6. 人群计数 P2PNet 算法使用笔记
  7. JavaSE02-IO
  8. 语种切换_多语种跳转研究试运行全程直播:选择大于努力英语学习入门的方法分享...
  9. 大学计算机基础完整性约束,大一大学计算机基础题库含12份
  10. STM32F10x UART多字节接收,程序卡死