happens-before 简述

从 JDK 5 开始,Java 使用新的 JSR-133 内存模型。JSR-133 使用 happens-before 的概念来阐述操作之间的内存可见性。在 JMM 中,如果一个操作执行的结果需要对另一个操作可见,那么这两个操作之间必须要存在 happens-before 关系。这里提到的两个操作既可以是在一个线程之内,也可以是在不同线程之间。《JSR-133:Java Memory Model and Thread Specification》对 happens-before 关系的定义如下。

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

以上两点定义看上去是不是会有点矛盾?如果 A happens-before B,第一点说A的操作顺序必须排在B之前。第二点说两个操作之间存在 happens-before 关系,并不意味着 Java 平台的具体实现必须要按照 happens-before 关系指定的顺序来执行。如果重排序之后的执行结果,与按happens-before 关系来执行的结果一致,是可以重排序的,也就是说先执行A再执行B与先执行B再执行A的结果一致(要符合as-if-serial语义),B可以在A之前执行。那岂不是违反了第一条的第一个操作的执行顺序排在第二个操作之前?

这里举个例子

double pi = 3.14; // A
double r = 1.0; // B
double area = pi * r * r; // C

根据happens-before原则,以上代码存在

A happens-before B

B happens-before C

A happens-before C (根据happens-before的传递性推导出来的,之后会讲述)

这里 A happens-before B,但实际上执行时B可以排在A之前执行,因为A和B之间并没有依赖关系,重排序的执行结果并不会对程序的执行造成影响(符合as-if-serial语义)。这是不是造成了以上两个定义的矛盾?其实并不矛盾,以上的第一点是JMM对程序员的承诺从程序员的角度来说,可以这样理解 happens-before 关系:如果 A happens-before B,那么 Java 内存模型将向程序员保证——A操作的结果将对 B 可见,且 A 的执行顺序排在 B 之前。注意,这只是 Java 内存模型向程序员做出的保证!实际上JMM 其实是在遵循一个基本原则:只要不改变程序的执行结果(指的是单线程程序正确同步的多线程程序),编译器和处理器怎么优化都行。JMM 这么做的原因是:程序员对于这两个操作是否真的被重排序并不关心,程序员关心的是程序执行时的语义不能被改变(即执行结果不能被改变)。

happens-before 规则

《JSR-133:Java Memory Model and Thread Specification》定义了如下 happens-before 规则。

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

    如同上面的A happens-before B,B happens-before C
  2. 监视器锁规则:对一个锁的解锁,happens-before 于随后对这个锁的加锁。
public class Test {
public int i = 0; public synchronized void writer(){ //1
i++; //2
} //3 public synchronized int reader(){ //4
return i; //5
} //6 }
/**
* 假设线程 A 执行 writer()方法,随后线程 B 执行 reader()方法。根据 happens-before规则,这个过程包含的 happens-before 关系可以分为 3 类。
* 根据程序次序规则,1 happens-before 2,2 happens-before 3;4 happens-before 5,5 happens-before 6。
* 根据监视器锁规则,3 happens-before 4。
* 根据 happens-before 的传递性,2 happens-before 5。
*/
  1. volatile 变量规则:对一个 volatile 域的写,happens-before 于任意后续对这个volatile 域的读。
class VolatileExample {
int a = 0;
volatile boolean flag = false;
public void writer() {
a = 1; // 1
flag = true; // 2
}
public void reader() {
if (flag) { // 3
int i = a; // 4
}
}
}
//假设线程 A 执行 writer()方法之后,线程 B 执行 reader()方法。根据 happens-before规则,这个过程建立的 happens-before 关系可以分为 3 类:
//根据程序次序规则,1 happens-before 2;3 happens-before 4。
//根据 volatile 规则,2 happens-before 3。
//根据 happens-before 的传递性规则,1 happens-before 4。
  1. 传递性:如果 A happens-before B,且 B happens-before C,那么 A happens-before C。
  2. start()规则:如果线程 A 执行操作 ThreadB.start()(启动线程 B),那么 A 线程的ThreadB.start()操作 happens-before 于线程 B 中的任意操作。
public class ThreadExample {
public static int i = 0; public static void threadBStart(){
new Thread(new Runnable() {
@Override
public void run() {
int j = i; //4
}
},"B").start(); //3
} public static void main(String[] args) {
i++; // 1
threadBStart(); //2
}
}
//main线程开始,这个过程建立的 happens-before 关系可以分为 3 类:
//根据程序次序规则,1 happens-before 2; 3 happens-before 4
//根据start()规则,2 happens-before 3; 2 happens-before 4;
//根据 happens-before 的传递性规则,1 happens-before 4。
  1. join()规则:如果线程 A 执行操作 ThreadB.join()并成功返回,那么线程 B 中的任意操作 happens-before 于线程 A 从 ThreadB.join()操作成功返回。
public class JoinExample {
public static int i = 0; public static Thread threadBStart(){
Thread threadB = new Thread(new Runnable() {
@Override
public void run() {
i ++;
}
}, "B");
threadB.start();
return threadB;
} public static void main(String[] args) throws InterruptedException {
threadBStart().join();
int a = i;
}
}

//线程b的所有操作happens-before main线程 join方法返回后

参考

《Java并发编程的艺术》

happens-before 原则的更多相关文章

  1. javascript的api设计原则

    前言 本篇博文来自一次公司内部的前端分享,从多个方面讨论了在设计接口时遵循的原则,总共包含了七个大块.系卤煮自己总结的一些经验和教训.本篇博文同时也参考了其他一些文章,相关地址会在后面贴出来.很难做到 ...

  2. 让姑姑不再划拳 码农也要有原则 : SOLID via C#

    “姑娘,别这样.我们是有原则的.” “一个有原则的程序猿是不会写出 “摧毁地球” 这样的程序的,他们会写一个函数叫 “摧毁行星”而把地球当一个参数传进去.” “对,是时候和那些只会滚键盘的麻瓜不同了, ...

  3. java面向对象六原则一法则

    1. 单一职责原则:一类只做它该做的事. 2. 里氏替换原则:子类必须能够替换基类(父类),否则不应当设计为其子类. 3. 依赖倒换原则:设计要依赖于抽象而不是具体化. 4. 接口隔离原则:接口要小而 ...

  4. SOLID 设计原则

    SOLID 原则基本概念: 程序设计领域, SOLID (单一功能.开闭原则.里氏替换.接口隔离以及依赖反转)是由罗伯特·C·马丁在21世纪早期 引入的记忆术首字母缩略字,指代了面向对象编程和面向对象 ...

  5. Java程序员应该了解的10个面向对象设计原则

    面向对象设计原则: 是OOPS(Object-Oriented Programming System,面向对象的程序设计系统)编程的核心,但大多数Java程序员追逐像Singleton.Decorat ...

  6. Atitit.研发团队的管理原则---立长不立贤与按资排辈原则

    Atitit.研发团队的管理原则---立长不立贤与按资排辈原则 1. 组织任命原则概述1 2. 历史的角度看,大部分组织使用的立长不立贤原则1 3. 论资排辈 立长不立贤原则1 3.1. 资格和辈分是 ...

  7. DIP原则、IoC以及DI

    一.DIP原则 高层模块不应该依赖于底层模块,二者都应该依赖于抽象. 抽象不应该依赖于细节,细节应该依赖于抽象. 该原则理解起来稍微有点抽象,我们可以将该原则通俗的理解为:"依赖于抽象&qu ...

  8. TDD原则

    TDD 介绍 测试驱动开发,或者叫 TDD,是一个敏捷方法,通过确保在代码是先前手动编写测试用 例,用测试来驱动开发,从而翻转开发生命周期(它不只是作为一种校验工具). TDD 的原则很简单的: 只有 ...

  9. 【PRINCE2是什么】PRINCE2认证之七大原则(7)

    我们先来回顾一下,PRINCE2七大原则分别是持续的业务验证,经验学习,角色与责任,按阶段管理,例外管理,关注产品,剪裁 第七个原则:根据项目环境剪裁 PRINCE2的价值在于它是一个通用的项目管理方 ...

  10. 【PRINCE2是什么】PRINCE2认证之七大原则(6)

    我们先来回顾一下,PRINCE2七大原则分别是持续的业务验证,经验学习,角色与责任,按阶段管理,例外管理,关注产品,剪裁 第六个原则:关注产品 PRINCE2指出,一个成功的项目必须以产品为导向,而不 ...

随机推荐

  1. 阿里云 AIGC 白嫖 FC 搭建 stable diffusion

    下午瞎逛在 V 站看到阿里在做推广,正好这几天在研究 stable-diffusion,就进去看了看,活动地址: https://developer.aliyun.com/topic/aigc . 主 ...

  2. Windows的Mysql5.7社区版的安装详细操作,从无到有,安装配置一条龙服务。(压缩包自行安装,非installer安装)

    换了一个电脑,所有软件.环境都得重新来安装一次,安装到Mysql的时候,发现网上有两种安装方式,一种是Mysql的压缩包安装方式,这种方式直接到官网下载Mysql的压缩包,解压之后做些配置就可以了,另 ...

  3. 大米cms爆破后台及支付逻辑漏洞

    又找到个网站挖洞,我来康康. 大米手机是个什么鬼手机??看一下吧 这个支付页面好熟悉,可能存在支付逻辑漏洞,咱们用burp改个包看看. 先支付一个看看 把包里那个=1改成0试试~ 证实确实存在支付逻辑 ...

  4. 2023-04-30:用go语言重写ffmpeg的resampling_audio.c示例,它实现了音频重采样的功能。

    2023-04-30:用go语言重写ffmpeg的resampling_audio.c示例,它实现了音频重采样的功能. 答案2023-04-30: resampling_audio.c 是 FFmpe ...

  5. 2022-09-23:整数数组 stations 表示 水平数轴 上各个加油站的位置。给你一个整数 k 。 请你在数轴上增设 k 个加油站, 新增加油站可以位于 水平数轴 上的任意位置,而不必放在整数

    2022-09-23:整数数组 stations 表示 水平数轴 上各个加油站的位置.给你一个整数 k . 请你在数轴上增设 k 个加油站, 新增加油站可以位于 水平数轴 上的任意位置,而不必放在整数 ...

  6. 2022-04-29:厨房里总共有 n 个橘子,你决定每一天选择如下方式之一吃这些橘子: 吃掉一个橘子。 如果剩余橘子数 n 能被 2 整除,那么你可以吃掉 n/2 个橘子。 如果剩余橘子数 n 能被

    2022-04-29:厨房里总共有 n 个橘子,你决定每一天选择如下方式之一吃这些橘子: 吃掉一个橘子. 如果剩余橘子数 n 能被 2 整除,那么你可以吃掉 n/2 个橘子. 如果剩余橘子数 n 能被 ...

  7. Selenium - 元素等待(1) - 强制等待/隐式等待

    Selenium - 元素等待 浏览器的等待可以分为三种:强制等待.隐式等待.显式等待. 强制等待 强制等待使用python自带的 time 模块: time.sleep(second):强制等待,无 ...

  8. vue之关闭eslint及vue/require-v-for-key 、vue/no-unsed-vars报错解决方法

    报错:Page1组件已经注册但是没有被使用.  vue/no-unused-components 如果报错以下: 解决方法: 首先打开项目中的package,json 找到eslint-config ...

  9. js 关于 replace 取值、替换第几个匹配项

    〇.前言 在日常开发中,经常遇到针对字符串的替换.截取,知识点比较碎容易混淆,特此总结一下,仅供参考. 一.替换第一个匹配项 字符串替换 let strtest = "0123测试repla ...

  10. web自动化03-xpath定位

      目标: Xpath方法   1.定位一组元素的方法   element = driver.find_elements_by_*("*")      * 可以是name,tag_ ...