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

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

从JDK5开始,Java基于Happens-Before概念阐述操作间的内存可见性。本文介绍了Happens-Before的定义、规则,包括程序次序、管程锁定、volatile变量规则等,并对规则进行详解。还说明了Happens-Before原则与时间顺序基本无关,排查线程安全问题应以该原则为准。

Happens-Before

从jdk5开始,java使用新的JSR-133内存模型,基于Happens-Before的概念来阐述操作之间的内存可见性。

Happens-Before定义

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

注意:不能将Happens-Before理解为它的字面意思,可以理解为“先行发生”,如A先行发生于B,就是说B执行之前,A产生的影响(修改共享变量、发送消息、调用方法等)可以被B观察到。(一团浆糊…继续挖)

Happens-Before规则

Happens-Before的八个规则(摘自《深入理解Java虚拟机》12.3.6章节):

  1. 程序次序规则:一个线程内,按照代码顺序,书写在前面的操作先行发生于书写在后面的操作;
  2. 管程锁定规则:一个unLock操作先行发生于后面对同一个锁的lock操作;(此处后面指时间的先后)
  3. volatile变量规则:对一个变量的写操作先行发生于后面对这个变量的读操作;(此处后面指时间的先后)
  4. 线程启动规则:Thread对象的start()方法先行发生于此线程的每个一个动作;
  5. 线程终结规则:线程中所有的操作都先行发生于线程的终止检测,我们可以通过Thread.join()方法结束、Thread.isAlive()的返回值手段检测到线程已经终止执行;
  6. 线程中断规则:对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生;
  7. 对象终结规则:一个对象的初始化完成先行发生于他的finalize()方法的开始;
  8. 传递性:如果操作A先行发生于操作B,而操作B又先行发生于操作C,则可以得出操作A先行发生于操作C;

Happens-Before规则详解

程序次序规则

同一个线程内,书写在前面的操作先行发生于书写在后面的操作:在网上有看到过很多文章,但是实际编译时经过指令重排序,有些情况下书写在后面的代码会先于前面的代码。Happens-Before可以理解为前面代码的执行结果对于后面代码是可见的(…怎么说有点绕,看例子吧)。

int a = 3;     //代码1
int b = a + 1; //代码2

上面的代码中,因为代码2的计算会用到代码1的运行结果,此时程序次序规则就会保证代码2中的a一定为3,不会是0(默认初始化的值),所以JVM不允许操作系统对代码1、2进行重排序,即代码1一定在代码2之前执行。下面的例子就无法保证执行顺序:

int a = 3; //代码1
int b = 2; //代码2

上面的代码中,代码1、2之间没有依赖关系,所以指令重排序有可能会发生,b的初始化可能比a早。

管程锁定规则

一个unLock操作先行发生于后面对同一个锁的lock操作:同一个锁只能由一个线程持有,下面举例

public class TestHappenBefore {
    public static int var;
    private static TestHappenBefore happenBefore = new TestHappenBefore();

    public static TestHappenBefore getInstance() {
        return happenBefore;
    }

    public static void main(String[] args) throws InterruptedException {
        new Thread(() -> TestHappenBefore.getInstance().method2()).start();
        new Thread(() -> TestHappenBefore.getInstance().method1()).start();
        new Thread(() -> TestHappenBefore.getInstance().method3()).start();
    }

    public synchronized void method1() {
        var = 3;
        System.out.println("method1,var:" + var);
    }

    public synchronized void method2() {
        try {
            System.out.println("线程2开始睡觉了~");
            new Thread().sleep(5000);
            System.out.println("线程2睡好了~");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        int b = var;
        System.out.println("method2,var:" + var + ",b:" + b);
    }

    public void method3() {
        synchronized (new TestHappenBefore()) { //换了把新锁
            var = 4;
            System.out.println("method3,var:" + var);
        }
    }
}
执行结果:
线程2开始睡觉了~
method3,var:4
线程2睡好了~
method2,var:4,b:4
method1,var:3

通过上面的例子我们发现,当线程2在“睡觉”的时间段内,线程1并没有执行,因为此时happenBefore对象的锁被线程2持有,线程2释放锁之前,线程1无法持有该锁,这符合管程锁定规则,还发现线程2“睡觉”的时候,线程3并没有停下,仍然执行了自己的代码,是因为method3的锁和线程2不是同一把锁,所以不受管程锁定规则的限制。

volatile变量规则

对一个变量的写操作先行发生于后面对这个变量的读操作(此处后面指时间的先后):这条规则保证了volatile变量的可见性,线程A写volatile变量后,线程B读volatile变量,则B读到的一定是A写的值,照旧举例(没有写出合适的案例,附上伪代码说明,如有合适的案例,请指教):

volatile int a;
//线程1执行内容
public void method1() {
    a = 1;
}
//线程2执行内容
public void method2() {
    int b = a;
}

如果线程1先执行,线程2再执行,则volatile变量规则可以保证线程2读取的变量a的值为1。

传递性

如果操作A先行发生于操作B,而操作B又先行发生于操作C,则可以得出操作A先行发生于操作C(感觉类似数学的传递性:A>B,B>C则A>C…),照旧一例:

volatile int var;
int b;
int c;
//线程1执行内容
public void method1() {
    b = 4; //1
    var = 3; //2
}
//线程2执行内容
public void method2() {
    c = var; //3
    c = b; //4
}

假设执行顺序为 1、2、3、4,由于单线程的程序次序规则,得出1 Happen Before 2,3 Happen Before 4,又因为volatile变量规则得出2 Happen Before 3,所以1 Happen Before 3,1 Happen Before 4(传递性),即最后变量c的值为4;若执行顺序为1、3、4、2,因为3、2没有匹配到Happen Before规则,所以无法通过传递性推测出传递关系,也就无法保证最后变量c的值为4,也可能为0(b初始化的值,没有读到线程1写入的值)

线程启动规则、线程终结规则、线程中断规则、对象终结规则四个规则相对比较易于理解,不再赘述。

Happens-Before原则与时间顺序的关系

前面提到不可以将Happens-Before理解为它的字面意思,即不能站在时间顺序的角度去理解先行发生原则,通过下面的例子来验证一下:

private int value = 0;
public void setValue(int value){
    this.value = value;
}
public void getValue(){
    return value;
}

假设线程A调用setValue(1)方法,线程B调用同对象的getValue()方法,线程A在时间上先执行,此时线程B调用方法的返回值是什么?
依次分析一下先行发生的八大原则:例子不在同一个线程内,故程序次序规则不适用;代码中没有同步块,所以管程锁定规则不适用;变量value没有被volatile关键字修饰,volatile变量规则同样不适用;线程启动规则、线程终结规则、线程中断规则、对象终结规则和本例没有关系。因为没有匹配到任何一条规则,所以传递性也不适用。通过执行结果(具有一定偶然性,实验时加大循环次数),我们会发现B的返回值有可能是1有可能是0,所以这个操作不是线程安全的。
解决方式有多种,例如:getter、setter方法加上synchronized同步块,就可以匹配上管程锁定规则;或者value变量用volatile关键字进行修饰,则可以匹配上volatile变量规则。
通过这个例子我们可以得出:“时间上的先发生”不代表这个操作是“先行发生”。
那“先行发生”的操作一定是“时间上的先发生”么?答案是否定的,最典型的例子就是我们常说的“指令重排序”,例子如下:

// 同一线程内
int i=1;
int j=1;

上面代码运行情况符合程序次序规则,按规则应该是“int i = 1;”的操作先行发生于“int j = 2;”,但“int j = 2;”有可能会先被处理器执行,这并不影响先行发生原则的正确性,因为我们的线程无法感知这点。
通过上面的两个例子,我们得出:时间的先后顺序和先行发生原则(Happen-Before原则)基本没有关系,所以我们在排查线程安全问题的时候不要受到时间顺序的干扰,一切以先行发生原则(Happen-Before原则)为准(摘自《深入理解Java虚拟机》12.3.6章节)。

文章参考:

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

相关推荐

简述 happens - before 八大规则

虽然"Happens-Before"关系定义了操作之间的顺序关系,但它不一定意味着操作的实际执行顺序。如果操作A发生在操作B之前,而操作B发生在操作C之前,那么操作A发生在操作C之前。在一个线程中,程序按照代码的顺序执行,这意味着一个线程中的操作会按照它们在源代码中的顺序执行。接口)在获取同一个锁之前的所有操作都发生在获取锁之后的操作之前。变量的写操作会在后续对该变量的读操作之前发生。方法在新线程开始执行之前,必须完成之前线程的所有操作。方法的调用发生在任何后续对该对象的操作之前。

学无止境 815

Happens-Before原则的通俗理解

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

pscool的博客 2667

【万字长文】学习Armv8架构虚拟化一篇就够了

这里我们将介绍一些基础的Hypervisor和虚拟化的理论知识。如果你已经有一定的基础或是已经熟悉了这些概念,可以跳过这部分内容。我们用Hypervisor这个词来定义一种负责创建,管理以及调度虚拟机(Virtual Machines, VMs)的软件。Stage 2 转换允许Hypervisor控制虚拟机的内存视图。具体来说,其可以控制虚拟机是否可以访问特定的某一块物理内存,以及该内存块出现在虚拟机内存空间的位置。这种能力对于虚拟机的隔离和沙箱功能来说至关重要。

叫好与叫座虽然不是对立面,但想在同一个作品中达到双重效果很难。 5320

happens-before 原则

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

qq_43465706的博客 1455

强化学习入门8—深入理解DDPG

本文是强化学习入门系列的第八篇,前面我们讲Actor-Critic时提到了DDPG。DDPG是google DeepMind团队提出的一种用于输出确定性动作的算法,它解决了Actor-Critic 神经网络每次参数更新前后都存在相关性,导致神经网络只能片面的看待问题这一缺点。同时也解决了DQN不能用于连续性动作的缺点。

小菜羊的博客 4315

开源大模型本地部署实战:Llama、DeepSeek、Qwen选型与工程落地指南

大语言模型(LLM)本地部署已成为AI工程化核心环节,其本质不是比拼参数量或榜单分数,而是考察模型架构、推理引擎、量化支持、IDE集成与多模态扩展能力的系统性适配。Llama以纯净Transformer结构成为通用底座,依赖llama.cpp和GGUF量化实现跨平台轻量推理;DeepSeek深度优化代码与数学推理,通过原生OpenAPI、FlashAttention-2及tensor parallelism显著降低API延迟;Qwen则采用模块化设计,支持视觉编码器(Qwen-VL)、生物计算(Qwen-B

weixin_29952383的博客 396

Java多线程之先行发生原则(happens-before)

前面介绍了Java内存模型及内存屏障相关概念,这篇文章接着介绍多线程编程另外一个比较重要的概念:先行发生原则(happens-before)。 重要性 happens-before是判断数据是否存在竞争,线程是否安全的主要依据,通过这个原则,我们可以解决并发环境下两个操作之间是否可能存在冲突的所有问题。 happens-before是什么 它Java内存模型中针对两项操作定义的偏序关系。例如操作A...

老胡 668

ArcGIS Pro 拓扑编辑实战:从‘地图拓扑’到‘共享编辑’,手把手教你批量修改要素

本文详细介绍了ArcGIS Pro中的拓扑编辑功能,从地图拓扑配置到共享编辑技巧,手把手教你如何批量修改要素。通过实战案例展示拓扑编辑在城市规划、地籍管理等场景中的应用,显著提升编辑效率并减少拓扑错误,是现代GIS技术的重要生产力工具。

weixin_30500473的博客 505

JVM学习1: Happens-Before 先行发生原则

Happens-Before 先行发生原则 参考资料: 《深入理解Java虚拟机》(周志明) 1. Happens-Before先行发生原则的是啥? 官方定义[https://docs.oracle.com/javase/specs/jls/se8/html/jls-17.html#jls-17.4.5] 具象的描述一下什么叫先行发生原则Happens-Before 如果有两个操作A,B A 在时间点 T1 执行 B 在时间点 T2 执行, 且 T2 在 T1 之后 若 A 的操作,能被 B 操作观测到

zyjzyj2的博客 459

iPhone免越狱Git服务器搭建教程-iSH

iPhone免越狱Git服务器搭建教程

jts666的博客 4428

SAP-HR 模块 PPOSE 的详细信息标签页增强

步骤1:T-CODE : SE11 创建一个名为 HRI9106 的结构 命名:HRI + 91 + 两位数字。人事管理 - 组织管理 - 集成新信息类型 - 将信息类型添加为明细范围中的标签页。步骤6:SE51 查看 7000屏幕,修改屏幕属性 让其更美观。步骤3:T-CODE : SM30维护 T777I 表。步骤5:PPCI 新建 7000屏幕用于显示。标签页 IT 开头 + 信息类型 9106。步骤2:T-CODE : PPCI。步骤4:T-CODE : SPRO。

常回来看看呀!! 1628

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

先行发生原则可以判断数据是否存在竞争,线程是否安全。 如果两个操作之间缺乏 happens-before关系,那么 JVM 可以对它们任意地排序。

CrazyTiger的博客 653

【NLG】(六)文本生成评价指标—— ROUGE原理及代码示例

前奏: 【NLG】(一)文本生成评价指标——BLEU原理及代码示例 【NLG】(二)文本生成评价指标—— METEOR原理及代码示例 【NLG】(三)文本生成评价指标—— ENTROPY原理及代码示例 【NLG】(四)文本生成评价指标—— diversity原理及代码示例 【NLG】(五)文本生成评价指标—— kl_divergence原理及代码示例 1.ROUGE原理 2.优缺点 3.如何算ROUGE ...

凝眸伏笔的博客 1万+

【JUC】十八、happens-before先行发生原则

如果线程A的操作(x=5)先行发生于线程B的操作(y=x),或者说这两个事件存在先行发生原则,那y=5一定成立,反之则不一定,因为x=5的改变可能还没从A线程的工作内存刷回主内存,线程就暂时挂起了。现在有线程A和线程B,线程A(在时间上先)调用了setValue方法,然后线程B调用同一对象的getValue方法,那线程B的返回值是?:对一个volatile变量的写操作先行发生于后面对这个变量的读操作,前面的写对后面的读是可见的,这里的”后面“同样是指时间上的先后。一定是先发烧了,温度计才能检测到体温变了。

llg___的博客 1659

【华为OD机试真题 新系统】1021、API请求日志去重分析 | 机试真题+思路参考+代码解析(C++、Java、Py、C语言、JS)

【华为OD机试真题 新系统】1021、API请求日志去重分析 | 机试真题+思路参考+代码解析(C++、Java、Py、C语言、JS) 华为od机试,华为od机试真题,华为od机试E卷,华为odE卷,华为od机试真题E卷,华为OD,华为OD机试,华为OD机试真题,华为od机考,华为od机试D卷,华为od机试真题D卷,华为od手撕代码,华为od,华为od机试C卷,华为od e卷,华为2026e卷,od机试,2026E卷,华为OD机试E卷,什么是华为od,华为od介绍,全面了解华为od,2026华为od,如何备

KJ.JK 146

OV9734 DATASHEET,完整规格书

OV9734完整规格书,包括电气参数,尺寸规格,寄存器配置表等

先行发生原则(happens-before)介绍

一、什么是先行发生原则 二、Java内存模型中的先行发生关系 三、如何应用先行发生规则 如果Java内存模型中所有的有序性都仅仅靠volatile和synchronized来完成,那么有一些操作将会变得很烦琐,但是我们在编写Java并发代码的时候并没有感觉到这一点,这是因为Java语言中有一个“先行发生”(happens-before)的原则。 这个原则非常重要,它是判断数据是...

栋先生 3442

VC中sqlite的使用

在VC中对sqlite的使用,实现数据库大批数据的快速插入,读取数据库中的数据在ListCtrl中显示

JVM Happens-Before 先行发生原则

JVM先行发生原则 如果Java内存模型中所有的有序性都仅靠volatile和synchronized来完成, 那么有很多操作都将会变得非常啰嗦, 但是我们在编写Java并发代码的时候并没有察觉到这一点, 这是因为Java语言中有一个“先行发生”(Happens-Before) 的原则。 这个原则非常重要, 它是判断数据是否存在竞争, 线程是否安全的非常有用的手段。 依赖这个原则, 我们可以通过几条简单规则一揽子解决并发环境下两个操作之间是否可能存在冲突的所有问题, 而不需要陷入Java内存模型苦涩难懂的定

the Blog of Forward 357

Camera2Demo

在Camera2 的基础上进行封装,使得Camera2一堆初始化逻辑与Activity分离,降低耦合度,完成预览拍照并保存的操作

TMS_WebGMaps_1.9.8.0_Full_Source

TMS_WebGMaps_1.9.8.0_Full_Source 全源码 delphi cbuilder

Happens-Before 先行发生原则

目录 先行发生是Java内存模型中定义的两项操作之间的偏序关系。 程序次序规则(Program Order Rule) 管程锁定规则(Monitor Lock Rule) volatile 变量规则(Volatile Variable Rule) 线程启动规则(Thread Start Rule) 线程终止规则(Thread Termination Rule) 线程中断规则(Thread Interruption Rule) 对象终结规则(Finalizer Rule) 传递性(Trans

qq_37084673的博客 383

JUC-happens-before先行发生原则

在JMM中,如果一个操作执行的结果需要对另一个操作可见性,或者代码重排序,那么这两个操作之间必须存在happens-before关系。 总原则: 1、如果一个操作happens-before另一个操作,那么第一个操作的执行结果将对第二个操作可见,而且第一个操作的执行顺序排在第二个操作之前。 2、两个操作之间存在happens-before关系,并不意味着一定要按照happens-before原则制定的顺序来执行。如果重排序之后的执行结果已按照happens-before关系来执行的结果一致,那么这种重

江黎 328

先行发生原则(Happens-before)

先行发生原则(Happens-Before)是判断数据是否存在竞争、线程是否安全的主要依据。 先行发生是Java内存,模型中定义的两项操作之间的偏序关系,如果操作A先行发生于操作B,那么操作A产生的影响能够被操作B观察到。 Java内存模型中存在的天然的先行发生关系: 1. 程序次序规则:同一个线程内,按照代码出现的顺序,前面的代码先行于后面的代码,准确的说是控制流顺序,因为

DR的专栏 3782

happens-before(先行发生原则)

JSR-133使用happens-before的概念来阐述操作之间内存的可见性,在JMM中如果一个操作的结果需要对另一个可见,那么这两个操作之间必须要存在happens-before规则 注意:两个操作之间有happens-before关系,并不意味着前一个操作必须要在后一个操作之前执行。happens-before仅仅要求前一个操作的(执行的结果)对后一个操作可见。 JMM的设计思想:程序员...

lm1060891265的博客 1115

JMM-多线程先行发生原则happens-before

方法一:把getter/setter方法都定义为synchronized方法------->不好,重量锁,并发性下降。方法二:把Value定义为volatile变量,由于setter方法对value的修改不依赖value的原值,满足。在JVM中,如果一个操作执行的结果需要对另一个操作可见或者代码重排序,那么这两个操作之间。必须存在happens-before(先行发生)原则,逻辑上的先后关系。volatile关键字使用场景。

兴趣是最好的老师 225

JMM之先行发生原则(happens-before)详解

在JMM规范下,如果一个操作执行的结果需要对另一个操作可见,那么这两个操作之间必须存在happers-before(先行发生)原则。

全栈行动派的博客 1072

java并发编程基础--先行发生原则( happens-before)与指令重排序

一、先行发生原则 happens-before 判断数据是否有竞争、线程是否安全的主要依据         1. 程序次序规则:同一个线程内,按照代码出现的顺序,前面的代码先行于后面的代码,准确的说是控制流顺序,因为要考虑到分支和循环结构。         2. 管程锁定规则:一个unlock操作先行发生于后面(时间上)对同一个锁的lock操作。         3. volatile变量规...

suhand的博客 561

Java先行发生(happens-before)原则

Java语言中存在一个“先行发生”原则,可以用于判断线程是否安全,数据是否存在竞争。 定义: 如果操作A先行发生于操作B,那么在操作B之前,操作A产生的影响能被操作B观察到。 Java内存模型存在以下先行发生关系,无须额外的同步操作。而不满足以下规则的两个操作,就没有顺序性保障,虚拟机可以对其进行指令重排序。 程序次序规则(Program Order Rule):在一个线程内,按照代码顺序(

Dandy613的博客 720
上一篇: 常见排序算法:冒泡排序
下一篇: 包装类的缓存及自动装箱、拆箱
小龙人_Feng
博客等级 码龄9年 1粉丝 23原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值