Happens-Before原则

Happens-Before原则深入解读 Happens-Before(先行发生)原则是对Java内存模型(JMM)中所规定的可见性的更高级的语言层面的描述。用这个原则解决并发环境下两个操作之间的可见性问题,而不需要陷入Java内存模型苦涩难懂的定义中。... 阅读详情

一、让人又爱又恨的指令重排

了解过Java并发编程知识的童鞋都知道,Java内存模型是围绕着并发过程中如何处理原子性、可见性和有序性3个特征来建立的,其中有序性最为复杂。
我们习惯性的认为代码总是从先到后、依次执行的,这在单线程的时候确实是没错的(至少程序是正确的运行的)。但在并发时,有时候给人感觉写在后面的代码,比写在前面的代码先执行,如同出现了幻觉。这就是鼎鼎大名的指令重排,指令重排是很有必要的,因为大大提高了cpu处理性能。
然而,指令重排,在提高了性能的同时,也会发生一些意想不到的灾难,举个栗子:

class UnsafeOrderExample {
  int x = 0;
  boolean v = false;
  public void writer() {
    x = 42;
    v = true;
  }
  public void reader() {
    if (v == true) {
      // 这里 x 会是多少呢?
      System.out.print(x);
    }
  }
}

对于上面的代码,如果只有一个线程,先执行writer方法,然后再执行reader方法,会得到一个跟预期一致的结果是42。
但假设有两个线程A、B,两者同时分别执行writer和reader方法,最终reader会输出什么呢?很显然,答案是不固定的,有可能输出42,也有可能输出0;这是因为writer方法中的代码有可能发生指令重排,导致v=true有可能会发生在x=42之前。这个类是线程不安全类。
指令重排是必要的,但同时它又带来了一些麻烦,这可怎么办?别急,Java对此指定了Happens-Before规则,既然不能禁止指令重排,那就用规则对指令重排作约束,正所谓“爱,就是克制”嘛。

二、Happens-Before规则

正如前面所说,虽然jvm和执行系统会对指令进行一定的重排,但也是建立在一些原则上的,并非所有指令都可以随便改变执行位置。这些原则就是Happens-Before原则。Happens-Before可以直译为“先行发生”,但其想表达的更深层的意思是“前面操作的结果对后续操作是可见的”。所以比较正式的说法是:Happens-Before约束了编译器的优化行为,虽然允许编译器优化,但是编译器优化后一定要遵循Happens-Before原则。

1、程序顺序规则

程序顺序原则,指的是在一个线程内,按照程序代码的顺序,前面的代码运行的结果能被后面的代码可见。(准确的说,应该是控制流顺序而不是程序代码顺序,因为要考虑分支、循环等结构。)
举个简单栗子:

int a,b
a=1
b=a+1  //如果指令重排不遵循程序顺序原则,则b有可能等于1

如果指令重排不遵循程序顺序原则,以上的代码的b最终有可能等于1,而不是我们期望的2。这个原则就保证了程序语义的正确性,重排指令不允许改掉原来的代码语义。

2、传递性

传递性,指的是如果A Happens-Before于B,B Happens-Before于C,则A Happens-Before于C。这个是很好理解的。用白话说就是,如果A的操作结果对B可见,B操作结果对C可见,则A的操作结果对C也是可见的。

3、volatile变量规则

指对一个volatile变量的写操作,Happens-Before于后续对这个volatile变量的读操作。如果单单是理解这句话的意思,就是我们熟悉的禁用cpu缓存的意思,使得volatile修饰的变量读到永远是最新的值。
如果这个规则跟第二个规则“传递性”结合来看,会有什么效果呢?我们可以通过改一下上面的例程来看看:

class UnsafeExample {
  int x = 0;
  volatile boolean v = false;//v用volatile修饰
  public void writer() {
    x = 42;
    v = true;
  }
  public void reader() {
    if (v == true) {
      // 这里 x 会是多少呢?
      System.out.print(x);
    }
  }
}

对比这段代码跟第一个例子中的代码,变化的只是成员变量v用了volatile修饰,如果仅仅是用“volatile变量规则”来看,如果同样是线程A、B同时分别调用writer和reader,得到的不也是有42或者0两个结果么?
别慌,如果我们再结合“传递性”规则来看:

  • x=42 Happens-Before 于写变量v=true
  • 写变量v=true Happens-Before 于读变量 v=true

根据“传递性”,可以得出x=42 Happens-Before 于读变量v=true,是不是恍然大悟了呢?由此可以得出最终B线程执行的reader方法输出的x=42而不是0。而这个结果,是靠“volatile变量规则”+“传递性”推导出来的,凭直觉是比较难看出来的。经过这样一番修改后,这个类就变成了线程安全了。

4、锁规则

锁规则,指的是一个锁的解锁 Happens-Before 于后续对这个锁的加锁。
举个栗子:

synchronized (this) { // 此处自动加锁
  // x 是共享变量, 初始值 =10
  if (this.x < 12) {
    this.x = 12; 
  }  
} // 此处自动解锁

假设线程A执行完synchronized代码块后,x的值变成了12,线程B进入代码块时,可以看到线程A对x的修改,也就是能读到x==12。这个比较容易理解。

5、线程start()规则

指的是主线程A启动子线程B后,子线程B能看到主线程在启动线程B前的操作。
举个栗子:

Thread B = new Thread(()->{
  // 主线程调用 B.start() 之前
  // 所有对共享变量的修改,此处皆可见
  // 此例中,var==77
});
// 此处对共享变量 var 修改
var = 77;
// 主线程启动子线程
B.start();

此处,线程B能读到var==77。

6、线程join()规则

这个规则跟上一条规则有点类似,只不过这个规则是跟线程等待相关的。指的是主线程A等待子线程B完成(对B线程join()调用),当子线程B操作完成后,主线程A能看到B线程的操作。
举个栗子:

Thread B = new Thread(()->{
  // 此处对共享变量 var 修改
  var = 66;
});
// 例如此处对共享变量修改,
// 则这个修改结果对线程 B 可见
// 主线程启动子线程
B.start();
B.join()
// 子线程所有对共享变量的修改
// 在主线程调用 B.join() 之后皆可见
// 此例中,var==66

此处,主线程A能看到线程B对共享变量var的操作,也就是可以看到var==66。

7、线程的interrupt()规则

指的是线程A调用线程B的interrupt()方法,Happens-Before 于线程B检测中断事件(也就是Thread.interrupted()方法)。这个也很容易理解。

8、finalize()规则

指的是对象的构造函数执行、结束 Happens-Before 于finalize()方法的开始。

三、总结

Happens-Before原则非常重要,它是判断数据是否存在竞争、线程是否安全的主要一句,依靠这个原则,我们可以解决并发环境下两个操作之间是否存在冲突的所有问题。

Happens-before原则的理解 Happens-before 8条原则的理解 阅读详情

相关推荐

Isaacgym使用操作指南

Isaac Gym 是一个强大的仿真工具,特别适合那些需要进行大规模并行仿真和训练的机器人和强化学习任务。通过 GPU 加速、深度学习集成和丰富的物理仿真能力,Isaac Gym 能够显著提高仿真和训练效率,是机器人学和 AI 研究中的一大利器。

Tony Wey的博客 1万+

Happens-Before原则的通俗理解

指令重排&happens-before 原则 & 内存屏障:https://www.cnblogs.com/ITPower/p/13580691.html Java内存模型:https://www.cnblogs.com/yuanfy008/p/9252555.html

pscool的博客 2666

从光耦隔离到稳定驱动:信捷伺服与STM32的硬件设计陷阱与救赎

本文深入探讨了信捷伺服驱动器与STM32微控制器配合时的硬件设计关键问题,包括光耦隔离电路选型、电源电压选择、拨码开关设置等核心环节。针对伺服驱动器的稳定驱动需求,提供了从电路设计到抗干扰策略的完整解决方案,帮助工程师规避常见陷阱,提升工业控制系统的可靠性。

gold8的专栏 311

Happens-Before 原则

在 JMM 中,定义了一套 Happens-Before 原则,用于保证程序在执行过程中的可见性和有序性。Happens-Before 原则主要包括程序次序原则、volatile 变量原则、传递原则、锁定原则、线程启动原则、线程总结原则、线程中断原则和对象终结原则。在JMM中, 如果一个操作执行的结果需要对另一个操作可见,那么这两个操作之间必须要存在happens-before关系。

SuZhan0711 1113

GNSS差分码偏差(DCB)从原理到实战:如何避免数据处理中的常见错误

本文深入解析GNSS差分码偏差(DCB)的原理及其对高精度定位的关键影响。针对数据处理中因忽略DCB校正导致的常见错误,如单系统PPP高程偏差、多系统融合精度下降及电离层研究失真等,提供了实战解决方案与主流DCB产品获取、集成方法,帮助用户有效规避陷阱,提升数据处理精度。

k5l6m的博客 224

ADS使用Smith圆图进行阻抗匹配

在处理RF系统的实际应用问题时,总会遇到一些非常困难的工作,对各部分级联电路的不同阻抗进行匹配就是其中之一。一般情况下,需要进行匹配的电路包括天线与低噪声放大器(LNA)之间的匹配、功率放大器输出(RFOUT)与天线之间的匹配、LNA/VCO输出与混频器输入之间的匹配。匹配的目的是为了保证信号或能量有效地从“信号源”传送到“负载”。 阻抗匹配(impedance matching) 信号源内阻与所接传输线的特性阻抗大小相等且相位相同,或传输线的特性阻抗与所接负载阻抗的大小相等且相位相同,分别称为传输线的输

红茶绿茶和奶茶的博客 3万+

什么是happens-before原则?

前言 本文隶属于专栏《100个问题搞定Java虚拟机》,该专栏为笔者原创,引用请注明来源,不足和错误之处请在评论区帮忙指出,谢谢! 本专栏目录结构和参考文献请见100个问题搞定Java虚拟机 正文 happens-before 关系是用来描述两个操作的内存可见性的。如果操作 X happens-before 操作 Y,那么 X 的结果对于 Y 可见。 Java 内存模型底层是通过内存屏障(memory barrier)来禁止重排序的。 线程内的 happens-before 在同一个线程中,字节码的先

Shockang的博客 1万+

3D视觉-激光三角测量法的概述和原理

激光三角法位移测量的原理是,用一束激光以某一角度聚焦在被测物体表面,然后从另一角度对物体表面上的激光光斑进行成像,物体表面激光照射点的位置高度不同,所接受散射或反射光线的角度也不同,用。激光三角测量法是一种通过图像采集系统采集被测物表面漫反射的激光轮廓,进而基于设备已知的相对位置通过算法处理,获得被测物表面的三维轮廓数据,进而还原目标物体三维空间信息的方法。采用激光三角原理和回波分析原理进行非接触位置、位移测量的精密传感器, 广泛应用于位置、位移 、厚度、半径、形状、振动、距离等几何量的工业测量。

qq_20660115的博客 3919

happens-before 原则

1、次序原则:一个线程内,按照代码的顺序,写在前面的操作先行发生于写在后面的操作,也就是说前一个操作的结果可以被后续的操作获取(保证语义串行性,按照代码顺序执行)。问题描述:假设存在线程A和B,线程A先(时间上的先后)调用了 setValue() 方法,然后线程B调用了同一个对象的 getValue() 方法,那么线程B收到的返回值是什么?3、volatile 变量原则:对一个 volatile 变量的写操作先行发生于后面对这个变量的读操作,前面的写对后面的读是可见的,这里的后面同样指时间上的先后。

qq_43465706的博客 1455

机器人运动不流畅?手把手教你用ACCSET调整ABB/FANUC点位加速度

本文针对ABB和FANUC工业机器人运动不流畅、拐角抖动、到位震颤等常见问题,提供了详细的解决方案。核心在于使用ACCSET指令(ABB)或ACC选项(FANUC)调整机器人的点位加速度与减速度参数。文章通过实战案例与系统化调试流程,手把手指导工程师优化运动性能,实现平稳、精准的机器人运动控制。

gpu4optimizer的博客 284

Sqlserver2012评估期已过问题解决(启动数据库服务报错1705都是同样的问题,数据库过期未激活)

一、背景: 因为之前安装sqlserver2012忘记输入序列号,如今出现评估期已过的问题,前几天忙活半天,才解决,发现网 上叙述都非常凌乱,并且仅仅有大意,有的非常难操作。所以把我操作的过程和大家交流分享: 一定要有序列号的输入。不然会有很多其它的麻烦出现: 二、问题解决: 第一步:进入SQL2012配置工具中的安装中心。 第二步:再进入左側维护选项界面,然后选择选择版本号升级。 ...

堂前燕的博客 3266

Happens-before原则了解

Happens-before原则 看过网上很多,先记录一下 Happens-before规则 从JDK5开始,java使用新的JSR -133内存模型(本文除非特别说明,针对的都是JSR- 133内存模型)。JSR-133提出了happens-before的概念,通过这个概念来阐述操作之间的内存可见性。如果一个操作执行的结果需要对另一个操作可见,那么这两个操作之间必须存在happens-before关系。这里提到的两个操作既可以是在一个线程之内,也可以是在不同线程之间。 与程序员密切相关的happen

a_higher的博客 611

【VMware】win 10:VMware 15 虚拟机安装 win 7 系统

目录 一、准备虚拟机 二、win7 ghost ISO镜像文件下载 三、VMware新建虚拟机 四、VMware安装win7 五、解决vmware虚拟机屏幕没有适应窗口全屏问题 一、准备虚拟机 查看我的另一博文:【VMware】VMware Workstation Pro15 虚拟机永久激活破解安装教程 二、win7 ghost ISO镜像文件下载 百度网盘下载地址:点击前往下载...

顾三殇 —— 博客空间(软件测试) 11万+

Java Happens-Before 原则

Happens-Before原则核心就是表达在一些条件下,前面的操作对于后面的操作是可见的。它有六个条件,或者说是六条原则。一、线程中的顺序性原则这个最容易理解,这个原则是指在同一个线程中,按照程序的顺序,前面的操作Happens-Before后面的操作。也就说在同一个线程中,前面先修改的数据,对于后面的操作是可见的。下面举例说明:inta=1; if(a==1){ }上面代码中第...

天行健,君子以自强不息;地势坤,君子以厚德载物。 845

SAP B1中文培訓教材_SDK

公司内部培训资料,对开发工具 ,DIAPI, UIAPI,Add-ON有详细解说

温度PID控制PLC程序

温度PID稳定控制程序,适用于欧姆龙PLC,对其他PLC也具有很大参考价值

Happens-Before原则深度解析

Java内存模型中的Happens-Before原则是确保多线程程序正确性的关键机制。本文系统解析了八大核心规则:程序顺序、监视器锁、volatile变量、线程启动/终止、中断、终结器和传递性,并揭示了底层实现依赖的内存屏障技术。通过双重检查锁定等典型应用场景,展示了如何正确建立线程间可见性保证,同时指出了常见误区与验证工具。对比C++和Go的内存模型后,给出减少同步开销、合理使用volatile等优化建议,并探讨了Java 9+的演进方向。掌握这些原则是编写高效线程安全代码的基础。

weixin_47242663的博客 1500

TMS_WebGMaps_1.9.8.0_Full_Source

TMS_WebGMaps_1.9.8.0_Full_Source 全源码 delphi cbuilder

Happens-before原则

       Happens-before 定义如下:1.如果一个操作Happens-before另一操作,那么第一个操作结果对第二个操作可见.而且第一个执行操作顺序在第二个之前执行.2.两个操作之间存在Happens-before关系,并不意味着一定要按照Happens-before制定的顺序执行操作,如果重排序后与Happens-before顺序执行的操作结果一致,这样的重排序并不非法/   ...

u014262155的博客 1379

廖雪峰Python教程[完整版]

廖雪峰的python教程整理的,已经排好版了,而且还是完整的,适合小白

Happens-Before原则(先行发生原则)

Happens-Before 从jdk5开始,java使用新的JSR-133内存模型,基于Happens-Before的概念来阐述操作之间的内存可见性。 Happens-Before定义 如果一个操作Happens-Before另一个操作,那么第一个操作的执行结果将对第二个操作可见,而且第一个操作的执行顺序排在第二个操作之前。 两个操作之间存在Happens-Before关系,并不意味着一...

FengDuidui的博客 2331

java happens before_聊聊Java happens-before原则

happens-before原则定义如下:1. 如果一个操作happens-before另一个操作,那么第一个操作的执行结果将对第二个操作可见,而且第一个操作的执行顺序排在第二个操作之前。2. 两个操作之间存在happens-before关系,并不意味着一定要按照happens-before原则制定的顺序来执行。如果重排序之后的执行结果与按照happens-before关系来执行的结果一致,那么这...

weixin_30562977的博客 377

深入浅出Java中的Happens-Before原则!

深入浅出 Java 中的 Happens-Before 原则!🎉

qq_56158663的博客 1663

happens-before 原则是什么?

原则是 Java 并发模型的核心,它定义了操作之间的内存可见性保证,而非物理时间顺序。它通过一系列明确的规则(如锁、volatile、线程启动/终止等)建立操作间的偏序关系,并具有传递性。理解并正确运用规则是编写正确、高效并发程序的基础,它使得底层优化成为可能,同时为程序员提供了确保多线程行为可预测性的工具。当你正确使用了volatileLock等同步机制时,本质上就是在利用这些规则建立跨线程的关系,从而保证共享数据修改的可见性。

qq_26846741的博客 694

【Java】一文读懂happens-before原则

【Java】一文读懂happens-befor原则

代码说,代码让世界更美好。 818

什么是 happens-before 原则?

Java 中happens-before 原则,是在 JSR-133 中提出的。 原文摘要: • Each action in a thread happens-before every subsequent action in that thread. • An unlock on a monitor happens-before every subsequent lock on tha...

ConstXiong 1041

JAVA:什么是 happens-before 原则?

摘要: Java内存模型(JMM)通过happens-before原则解决多线程的可见性问题,确保操作A的结果对操作B可见且执行顺序正确。核心规则包括程序顺序、锁同步、volatile变量、线程启动/终止及传递性。例如: synchronized保证锁释放对后续加锁可见 volatile写操作对读操作可见 Thread.start()前操作对线程内可见 未建立happens-before可能导致线程间数据不可见(如非volatile变量)。

木头 1159
上一篇: @Transactional注解实现事务管理的原理
下一篇: 释放sqlite文件占用的多余空间
H阿布
博客等级 码龄12年 24粉丝 44原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值