首页 > 其他分享 >深入理解 happens-before 原则

深入理解 happens-before 原则

时间:2022-10-06 20:06:51浏览次数:60  
标签:happens Java 发生 线程 深入 先行 规则 操作 before

在前面的文章中,我们深入了解了 Java 内存模型,知道了 Java 内存模型诞生的意义,以及其要解决的问题。最终我们知道:Java 内存模型就是定义了 8 个基本操作以及 8 个规则,只要遵守这些规则的并发操作,那么它们就是安全的。

即使强如树哥的人,看了这 16 条规则也很头疼。它们太过于繁琐了,非常不利于我们日常代码的编写。为了能帮助编程人员理解,于是就有了与其相等价的判断原则 —— 先行发生原则,它可以用于判断一个访问在并发环境下是否安全。

到这里,我们需要明白:happens-before 原则是对 Java 内存模型的简化,让我们更好地写出并发代码。就像 Java 语言等高级语言,之于汇编语言、机器语言等低级语言一样,可以让编程人员免受「皮肉之苦」。

什么是 happens-before?

happens-before 指的是 Java 内存模型中两项操作的顺序关系。例如说操作 A 先于操作 B,也就是说操作 A 发生在操作 B 之前,操作 A 产生的影响能够被操作 B 观察到。这里的「影响」包括:内存中共享变量的值、发送了消息、调用了方法等。

举个很简单的例子:下面代码里 ​​i=1​​​ 在线程 A 中执行,而 ​​j=i​​​ 在线程 B 中执行。因为 ​​i=1​​​ 操作先于 ​​j=i​​​ 执行,那么 ​​i=1​​ 操作的结果就应该能够被线程 B 观察到。

// 在线程 A 中执行
i = 1;
// 在线程 B 中执行
j = i;

Java 内存模型下一共有 8 条 happens-before 规则,如果线程间的操作无法从如下几个规则推导出来,那么它们的操作就没有顺序性保障,虚拟机或者操作系统就能随意地进行重排序,从而可能会发生并发安全问题。

  • 程序次序规则(Program Order Rule):在一个线程内,按照程序代码顺序,书写在前面的操作先行发生于书写在后面的操作。准确地说,应该是控制流顺序而不是程序代码顺序,因为要考虑分支、循环等结构。
  • 管程锁定规则(Monitor Lock Rule):一个unlock操作先行发生于后面对同一个锁的lock操作。这里必须强调的是同一个锁,而“后面”是指时间上的先后顺序。
  • volatile变量规则(Volatile Variable Rule):对一个volatile变量的写操作先行发生于后面对这个变量的读操作,这里的“后面”同样是指时间上的先后顺序。
  • 线程启动规则(Thread Start Rule):Thread对象的start()方法先行发生于此线程的每一个动作。
  • 线程终止规则(Thread Termination Rule):线程中的所有操作都先行发生于对此线程的终止检测,我们可以通过Thread.join()方法结束、Thread.isAlive()的返回值等手段检测到线程已经终止执行。
  • 线程中断规则(Thread Interruption Rule):对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生,可以通过Thread.interrupted()方法检测到是否有中断发生。
  • 对象终结规则(Finalizer Rule):一个对象的初始化完成(构造函数执行结束)先行发生于它的finalize()方法的开始。
  • 传递性(Transitivity):如果操作A先行发生于操作B,操作B先行发生于操作C,那就可以得出操作A先行发生于操作C的结论。

极简实践案例

Java 语言无须任何同步手段保障,就能成立的先行发生规则,就只有上面这些了。下面举个例子来说明如何用这些规则去判断操作是否具备顺序性,是否是线程安全的。

private int value = 0;

public void setValue(int value){
this.value = value;
}

public int getValue(){
return value;
}

上面的代码是一组很普通的 getter/setter 方法。假设线程 A 和 B,线程 A 先(时间上的先后)调用了 ​​setValue(1)​​​,之后线程 B 调用了同一个对象的 ​​getValue()​​,那么线程 B 收到的返回值是什么?

我们依次分析一下先行发生原则中的各项规则:

  1. 首先,由于两个方法分别由线程A和线程B调用,不在一个线程中,所以程序次序规则在这里不适用。
  2. 接着,由于没有同步块,自然就不会发生lock和unlock操作,所以管程锁定规则不适用。
  3. 继续,由于value变量没有被volatile关键字修饰,所以volatile变量规则不适用。
  4. 继续,后面的线程启动、终止、中断规则和对象终结规则也和这里完全没有关系。
  5. 最后,因为没有一个适用的先行发生规则,所以最后一条传递性也无从谈起。

因此,即使我们知道线程 A 在操作时间上先于线程 B,但我们还是无法确定线程 B ​​getValue()​​ 方法的返回结果。换句话说,这里面的操作不是线程安全的。

那怎么修复这个问题呢?

我们至少有两种比较简单的方案可以选择:

  1. 第一种,要么把​​getter/setter​​​ 方法都定义为​​synchronized​​ 方法,这样就可以套用管程锁定规则。
  2. 第二种,要么把​​value​​​ 定义为​​volatile​​​ 变量,由于​​setter​​​ 方法对​​value​​​ 的修改不依赖​​value​​​ 的原值,满足​​volatile​​​ 关键字使用场景,这样就可以套用​​volatile​​ 变量规则来实现先行发生关系。

通过上面这个案例,我们知道:一个操作时间上线发生,不代表这个操作会「先行发生」。 那如果一个操作「先行发生」,是否就能推导出这个操作必定是时间上先发生呢?其实并不能,因为有可能发生指令重排序。

// 如下操作在同一个线程中执行
int j = 1;
int j = 2;

上述代码在同一线程中执行,根据程序执行次序规则,​​int i = 1;​​​ 的操作先行发生于 ​​int j = 2;​​​,但 ​​int j =2​​ 的代码有可能被处理器先执行,因为它们不相互依赖,不影响先行发生原则的正确性。

上述这两个案例综合起来证明了一个结论:时间先后顺序与先行发生原则之间基本没有太大的关系,所以我们衡量并发安全问题的时候不要受到时间顺序的干扰,一切必须以先行发生原则为准。

总结

happens-before 原则一共有 8 条原则,它是对 Java 内存模型规则的简化,帮助编程人员提高编程效率。

时间先后顺序与先行发生原则之间基本没有太大的关系,我们衡量并发安全问题的时候不要受到时间顺序的干扰,一切必须以先行发生原则为准。

深入理解 happens-before 原则_java



标签:happens,Java,发生,线程,深入,先行,规则,操作,before
From: https://blog.51cto.com/u_13879334/5733980

相关文章

  • 如何从分类层面,深入理解设计模式?
    学习过设计模式的都知道,设计模式分为三大类,分别是:创建型、结构型、行为型。但为什么它们这么分呢?某个设计模式为啥就属于结构型,而不属于行为型呢?创建型、结构型、行为型它......
  • 「前端料包」深入理解JavaScript原型和原型链
    1.前言关于JS原型和原型链我之前刚学js就有写过一篇学习笔记形式的博客,但前两天翻出来一看——什么鬼,这是我写的吗?自己都看不懂了,于是我重新整理思路,今天「前端料包」......
  • 深入理解 JavaScript
    原型和继承__proto__属性对象有一个隐藏属性[[Prototype]],指向其原型(父类型),如果没有原型则为null。从对象中读取一个不存在的属性时,会自动往原型中查找这个属性,这就......
  • 深入理解linux内核第三版(三)中断和异常
    中断:也叫异步中断,是由外设产生的。异常:也叫同步中断,是由CPU产生的,是指令执行过程中产生的。中断信号的作用:中断信号提供了一种特殊的方式,使处理器转而去运行正常控制流之......
  • 深入理解JS作用域链与执行上下文
    变量提升:变量提升(hoisting)。我可恨的var关键字:你读完下面内容就会明白标题的含义,先来一段超级简单的代码:<scripttype="text/javascript">varstr='Hello......
  • 深入理解AQS--jdk层面管程实现【管程详解的补充】
    什么是AQS1.java.util.concurrent包中的大多数同步器实现都是围绕着共同的基础行为,比如等待队列、条件队列、独占获取、共享获取等,而这些行为的抽象就是基于AbstractQ......
  • 干货 | 通用 api 封装实战,带你深入理解 PO
    在普通的接口自动化测试中,如果接口的参数,比如url,headers等传参改变,或者测试用例的逻辑、断言改变,那么整个测试代码都需要改变。apiobject设计模式借鉴了pageobject的设计模......
  • SAP UI5 Simple Form 属性 columnsL,columnsM,columnsXL 的属性深入剖析试读版
    一套适合SAPUI5初学者循序渐进的学习教程本专栏计划的文章数在​​300​​​篇左右,到​​2022年9月22日​​​为止,目前已经更新了​​133​​​篇,专栏完成度为​​......
  • 深入浅出DevOps:流水线任务改造
    ......
  • Redis核心设计原理(深入底层C源码)
    Redis基本特性1.非关系型的键值对数据库,可以根据键以O(1)的时间复杂度取出或插入关联值2.Redis的数据是存在内存中的3.键值对中键的类型可以是字符串,整......