在 Java 并发编程中,Java 内存模型(Java Memory Model, JMM)与 Happens-Before 关系是理解多线程数据可见性和有序性的核心理论。本文从 JMM 的抽象模型出发,系统解析 Happens-Before 规则的本质、应用场景及面试高频问题,确保内容深度与去重性。

Java 内存模型(JMM)核心抽象

JMM 的核心目标

  • 规范内存访问行为:定义线程与主内存(Main Memory)、工作内存(Working Memory)之间的数据交互规则,解决多线程环境下的可见性(Visibility)原子性(Atomicity)有序性(Ordering 问题。

  • 跨平台一致性:屏蔽不同硬件和操作系统的内存访问差异,确保 Java 程序在不同平台上的内存语义一致。

内存交互模型

线程内存视图

  • 主内存:所有线程共享的内存区域,存储对象实例、静态变量等共享数据(对应 JVM 堆的共享区域)。

  • 工作内存:每个线程私有的内存空间,存储主内存中变量的副本(实际是 CPU 缓存、寄存器等的抽象)。

  • 数据交互规则

  1. 线程对变量的所有操作(读 / 写)必须在工作内存中进行,不能直接操作主内存。
  2. 线程间无法直接访问彼此的工作内存,变量传递需通过主内存完成(如图 1 所示)。

图 1:线程 A 与线程 B 通过主内存交互数据

原子性保证

  • 基本原子操作

    • read/write:主内存与工作内存间的变量传输(非原子,需结合具体指令)。
    • lock/unlock:对变量的加锁 / 解锁(保证原子性,如synchronized的底层实现)。
  • JMM 原子性范围
    • 单个volatile变量的读 / 写具有原子性(64 位long/double除外,需-XX:+UseLargePages显式开启)。
    • 复合操作(如i++)不保证原子性,需通过AtomicInteger或锁实现。

有序性与指令重排序

  • 编译器优化重排序:编译器为提升性能对指令重新排序(需遵守 Happens-Before 规则)。
  • 处理器重排序:CPU 乱序执行指令(通过内存屏障指令保证有序性)。
  • JMM 有序性保证
    • 程序顺序规则:单线程内指令按程序顺序执行(Happens-Before 规则之一)。
    • volatile 规则:volatile 变量写操作后插入写屏障,读操作前插入读屏障,禁止重排序。

Happens-Before 关系:线程间通信的桥梁

Happens-Before 定义

JLS(Java 语言规范)定义的 Happens-Before 关系是一种偏序关系,用于判断在多线程环境下,一个操作的结果是否对另一个操作可见。若操作 A Happens-Before 操作 B,则 A 的执行结果对 B 可见,且 A 的执行顺序优先于 B。

关键性质

  • 不表示时间上的先后顺序,而是结果的可见性保证(时间先后不蕴含 Happens-Before,反之亦然)。
  • 是 JMM 定义的唯一线程间通信机制,所有可见性分析必须基于 Happens-Before 规则。

八大 Happens-Before 规则(JLS §17.4.5)

程序顺序规则(Program Order Rule)

  • 规则:单线程内,每个操作 Happens-Before 其后续的任意操作。
  • 示例
int a = 1;   // 操作1
int b = 2; // 操作2
// 操作1 Happens-Before操作2(单线程内顺序保证)

监视器锁规则(Monitor Lock Rule)

  • 规则:解锁操作 Happens-Before 后续对同一锁的加锁操作。

  • 示例

synchronized (lock) {
x = 10; // 解锁前的写操作对后续加锁后的读可见 } // 解锁操作(Happens-Before) ... synchronized (lock) { assert x == 10; // 成立 } // 加锁操作

volatile 变量规则(Volatile Variable Rule)

  • 规则:对 volatile 变量的写操作 Happens-Before 后续对该变量的读操作。

  • 实现原理

    • 写 volatile 时插入写屏障(Store Barrier),强制刷新工作内存到主内存。
    • 读 volatile 时插入读屏障(Load Barrier),强制从主内存读取最新值。
  • 反例

volatile int flag = 0;
// 线程A: flag = 1; // 写volatile(Happens-Before) x = 5; // 普通写操作,与线程B的y读无Happens-Before关系 // 线程B: if (flag == 1) {
assert x == 5; // 不保证成立(x未被volatile修饰)
}

线程启动规则(Thread Start Rule)

  • 规则Thread.start()操作 Happens-Before 线程内的第一个操作。
  • 应用
Thread thread = new Thread(() -> {
x = 10; // 线程内第一个操作,保证可见于调用thread.start()之后的代码 }); thread.start(); // 在thread.start()之后,x=10的写操作对其他线程可见(需配合volatile或锁)

线程终止规则(Thread Termination Rule)

  • 规则:线程内的最后一个操作 Happens-Before 对该线程的join()返回。
  • 示例
Thread thread = new Thread(() -> y = 20);

thread.start();
thread.join(); // join()返回时,y=20的写操作对当前线程可见 assert y == 20; // 成立

对象终结规则(Finalizer Rule)

  • 规则:对象的构造函数执行完毕 Happens-Before 其finalize()方法开始执行。

传递性规则(Transitivity)

  • 规则:若 A Happens-Before B 且 B Happens-Before C,则 A Happens-Before C。
  • 复合场景
volatile int flag;
int x;
// 线程A:
x = 10; // A1
flag = 1; // A2(volatile写,Happens-Before线程B的A3) // 线程B:
while (flag != 1); // B1(volatile读,等待A2的Happens-Before) assert x == 10; // B2(成立,因A1 Happens-Before A2,A2 Happens-Before B1,传递性导致A1 Happens-Before B2)

中断规则(Interruption Rule)

  • 规则interrupt()调用 Happens-Before 被中断线程检测到中断事件(isInterrupted()interrupted())。

Happens-Before 与可见性分析实战

经典案例:双重检查锁定(DCL)的线程安全

public class Singleton {
private static volatile Singleton instance; // 必须加volatile
private Singleton() {} public static Singleton getInstance() { if (instance == null) { // 1. 第一次检查 synchronized (Singleton.class) { // 2. 加锁 if (instance == null) { // 3. 第二次检查 instance = new Singleton(); // 4. 构造对象
}
}
}
return instance;
}
}
  • 关键分析
  1. 若无volatile,步骤 4(对象构造)可能被重排序为 “分配内存→设置 instance 引用→初始化对象”,导致其他线程通过步骤 1 获取到未初始化的 instance。
  2. volatile保证步骤 4 的写操作 Happens-Before 步骤 1 的读操作(结合监视器锁规则与传递性),避免重排序。

对比:synchronized 与 volatile 的 Happens-Before 范围

特性 synchronized volatile
可见性保证 锁释放时刷新主内存,锁获取时读取主内存 写时刷新主内存,读时读取主内存
有序性保证 锁范围内的指令不被重排序到锁外 禁止 volatile 变量的前后指令重排序
Happens-Before 锁释放 Happens-Before 后续加锁 写操作 Happens-Before 后续读操作
原子性 保证代码块的原子性 保证单个变量的读 / 写原子性

面试高频问题深度解析

基础概念类问题

  • Q:Happens-Before 是否等同于时间上的先行?

    A:否。Happens-Before 是 JMM 定义的可见性规则,时间上先发生的操作不一定对后续操作可见(如无 Happens-Before 关系的普通变量读写)。
  • Q:为什么单线程内不需要考虑 Happens-Before?

    A:程序顺序规则保证单线程内操作的有序性,JMM 确保单线程行为与程序顺序一致,无需额外同步。

实战应用类问题

  • Q:如何利用 Happens-Before 规则证明 volatile 的可见性?

    A:
  1. 写 volatile 变量时,根据 volatile 规则,写操作 Happens-Before 后续读操作。
  2. 结合传递性,若 A 操作 Happens-Before 写 volatile,读 volatile 操作 Happens-Before B 操作,则 A 的结果对 B 可见。
  • Q:Happens-Before 如何解决指令重排序问题?

    A:通过内存屏障(如 volatile 的读写屏障、synchronized 的锁屏障)在指令间插入 Happens-Before 关系,限制重排序范围,确保可见性。

陷阱类问题

  • Q:以下代码是否保证线程 B 输出 x=10?
int x = 0;
volatile boolean flag = false;
// 线程A
x = 10; // A1
flag = true; // A2(volatile写)
// 线程B
while (!flag); // B1(volatile读)
System.out.println(x); // B2

A:。根据 volatile 规则,A2 Happens-Before B1,结合程序顺序规则(A1 Happens-Before A2),通过传递性,A1 Happens-Before B2,故 B2 输出 x=10。

总结:构建 Happens-Before 知识体系的三个维度

原理维度

  • 理解 JMM 的抽象模型(主内存与工作内存的交互规则),明确 Happens-Before 是 JMM 定义的唯一线程间通信机制。
  • 掌握八大规则的本质(如监视器锁规则的加锁 / 解锁语义,volatile 规则的屏障插入),区分规则的适用场景(如线程启动规则与 join () 的配合)。

应用维度

  • 能通过 Happens-Before 规则分析并发代码的可见性(如 DCL 为何需要 volatile,单例模式的线程安全证明)。
  • 对比不同同步机制(synchronized、volatile、Lock)的 Happens-Before 保证,选择合适的可见性解决方案。

面试应答维度

  • 面对 “可见性如何实现” 类问题,需结合 Happens-Before 规则与具体场景(如 volatile 的写 - 读规则,锁的释放 - 获取规则)。

  • 注意区分 Happens-Before 与先行发生原则,强调其作为 JMM 规范的理论基础,而非简单的时间顺序。

    通过将 Happens-Before 规则与 JMM 抽象模型深度结合,既能应对 “指令重排序如何影响可见性” 等底层问题,也能驾驭 “高并发场景下如何保证数据一致性” 等综合应用问题,展现对 Java 并发编程核心理论的系统化理解。

Java 内存模型与 Happens-Before 关系深度解析的更多相关文章

  1. java内存分配和String类型的深度解析

    [尊重原创文章出自:http://my.oschina.net/xiaohui249/blog/170013] 摘要 从整体上介绍java内存的概念.构成以及分配机制,在此基础上深度解析java中的S ...

  2. 【转】java内存分配和String类型的深度解析

    一.引题 在java语言的所有数据类型中,String类型是比较特殊的一种类型,同时也是面试的时候经常被问到的一个知识点,本文结合java内存分配深度分析关于String的许多令人迷惑的问题.下面是本 ...

  3. 《深入理解 Java 虚拟机》读书笔记:Java 内存模型与线程

    正文 由于计算机的处理器运算速度与它的存储和通信子系统速度的差距太大了,大量的时间都花费在磁盘 I/O.网络通信或者数据库访问上,导致处理器在大部分时间里都处于等待其他资源的状态.因此,为了充分利用计 ...

  4. Java内存模型深度解析:final--转

    原文地址:http://www.codeceo.com/article/java-memory-6.html 与前面介绍的锁和Volatile相比较,对final域的读和写更像是普通的变量访问.对于f ...

  5. Java内存模型深度解析:volatile--转

    原文地址:http://www.codeceo.com/article/java-memory-4.html Volatile的特性 当我们声明共享变量为volatile后,对这个变量的读/写将会很特 ...

  6. Java内存模型深度解析:基础部分--转

    原文地址:http://www.codeceo.com/article/java-memory-1.html 并发编程模型的分类 在并发编程中,我们需要处理两个关键问题:线程之间如何通信及线程之间如何 ...

  7. Java内存模型深度解析:顺序一致性--转

    原文地址:http://www.codeceo.com/article/java-memory-3.html 数据竞争与顺序一致性保证 当程序未正确同步时,就会存在数据竞争.java内存模型规范对数据 ...

  8. Java内存模型深度解读

    Java内存模型规范了Java虚拟机与计算机内存是如何协同工作的.Java虚拟机是一个完整的计算机的一个模型,因此这个模型自然也包含一个内存模型——又称为Java内存模型. 如果你想设计表现良好的并发 ...

  9. 浅析java内存模型--JMM(Java Memory Model)

    在并发编程中,多个线程之间采取什么机制进行通信(信息交换),什么机制进行数据的同步? 在Java语言中,采用的是共享内存模型来实现多线程之间的信息交换和数据同步的. 线程之间通过共享程序公共的状态,通 ...

  10. 了解Java内存模型,看完这一篇就够了

    前言(此文草稿是年前写的,但由于杂事甚多一直未完善好.清明假无事,便收收尾发布了) 年关将近,个人工作学习怠惰了不少.两年前刚做开发的时候,信心满满想看看一个人通过自己的努力,最终能达到一个什么样的高 ...

随机推荐

  1. MySQL-事务中的一致性读和锁定读的具体原理

    前言 上一篇文章MySQL-InnoDB行锁中,提到过一致性锁定读和一致性非锁定读,这篇文章会详细分析一下在事务中时,具体是如何实现一致性的. 一致性读原理 start transaction和beg ...

  2. gitee 删库跑路的正确打开方式

    前言 又是一个周一, 阳光一点都不明媚... 码云 gitee.com 五群 (QQ群号: 515965326) 又发生了一起删库跑路事件(手动滑稽). 手动部分截图 看图说话 为了更好的复现完整的流 ...

  3. Robot Framework绝对路径转相对路径

    如上图,添加商品需要上传商品图片,如此,设计脚本时候会填入图片的路径,使自动化能够自动到目的路径内获取图片上传 C:\\Users\\Beckham\\Desktop\\test2\\autoTest ...

  4. Spring Boot 根据配置决定服务(集群、单机)是否使用某些主件

    比如:在集群模式下,我想用 Nacos 组件,单机版不想用它. server: name: VipSoft Server Dev port: 8193 cloud: nacos: discovery: ...

  5. java.security.provider.getservice blocked

    JDK版本: JDK8u192 bug: https://bugs.openjdk.org/browse/JDK-8206333 堆栈: "Common-Business-Thread-57 ...

  6. XXL-CACHE v1.2.0 | 多级缓存框架

    Release Notes 1.[增强]多序列化协议支持:针对L2缓存,组件化抽象Serializer,可灵活扩展更多序列化协议: 2.[优化]移除冗余依赖,精简Core体积: XXL- CACHE ...

  7. CSS 魔法与布局技巧

    CSS 布局与视觉效果常用实践指南 在我一篇随笔中其实有说到十大布局,里面有提到 flex 布局.grid 布局.响应式布局,不过没有提到容器查询这个,现在说下这三个布局然后穿插下容器查询把. 1️⃣ ...

  8. 原生JS表格数据常用总结

    主要是在数据报表这块, 做了好几年发现, 其实用户最终想要看的并不是酷炫的BI大屏, 而是最基础也是最复杂的 中国式报表. 更多就是倾向于从表格中去获取数据信息, 最简单的就是最好的, 于是还是来总结 ...

  9. 【语义分割专栏】先导篇:常用数据集(VOC、Camvid、Cityscape、ADE20k、COCO)

    目录 前言 mask模式 PASCAL-VOC2012 下载 数据集简介 数据加载(dataloader) CamVid 下载 数据集简介 数据加载(dataloader) Cityscape 下载 ...

  10. Summary of Indexing operation in DataFrame of Pandas

    Summary of Indexing operation in DataFrame of Pandas For new users of pandas, the index of DataFrame ...