理解 Java 内存模型和 happens-before

在并发编程中,多个线程的执行顺序往往难以预测,若缺乏明确的规则,程序的行为可能变得不可控。Java内存模型(JMM)通过定义happens-before关系,为线程间的操作建立了一套时序约束,从而在混乱的并行时间线中划出确定的因果关系,帮助开发者编写正确的多线程代码。

无同步下的平行时间线

默认情况下,不同线程的指令执行如同平行宇宙,线程A的写操作可能不会立即被线程B观察到。例如,若没有同步措施,一个线程修改的变量值可能长期停留在本地缓存,导致其他线程读取到过期数据。多个线程的执行就像多条独立的时间线(如下图),彼此之间没有明确的先后关系:

less 复制代码
Thread 1:  A → B → C → D  
Thread 2:  X → Y → Z  
Thread 3:  P → Q → R  

为什么线程内部能保持确定的时间线?因为JMM规定了程序顺序规则:单线程内的操作按代码顺序执行(尽管可能存在指令重排序,但最终结果与顺序执行一致)。

同步:建立时间线的关联

JMM 的 happens-before 关系会在这些时间线之间建立部分顺序,形成跨线程的依赖箭头。例如:

less 复制代码
Thread 1:  A → B → C → D  
             ↘  
Thread 2:     X → Y → Z  
                ↗  
Thread 3:  P → Q → R  

JMM通过以下规则建立跨线程时序关系:

  1. 监视器锁规则:解锁操作 happens-before 后续的加锁操作。
  2. volatile 变量规则:volatile 变量的写 happens-before 后续的读。
  3. 线程启动规则:Thread.start() happens-before 新线程的所有操作。
  4. 线程结束规则:线程的所有操作 happens-before 其他线程检测到它的终止(如 Thread.join())。
  5. 中断规则。一个线程 interrupt 另一个线程,先于被中断线程检测到自己被中断(通过抛出 InterruptedException,或者调用 isInterrupted 和 interrupted)。
  6. 终结器规则。对象的构造函数先于启动该对象的终结器。

JMM 的传递性规则说明时间线概念模型里面的箭头也具有传递性。

示例:传递性与可见性

假设:

  • 线程 1 写入 volatile x = 1(写操作 W)。
  • 线程 2 读取 volatile x(读操作 R)。

根据 volatile 规则,W happens-before R。如果线程 1 的普通变量写 y = 2 在 W 之前,则由于传递性,y = 2 对线程 2 可见。

总结

happens-before关系是JMM的核心机制,它通过建立跨线程的"先后"关系来约束执行顺序,有效避免了数据竞争和内存可见性问题。程序员可以依赖这些规则编写正确的并发代码,而JVM则负责在底层实现这些保证。

在Java中,各线程原本就像互不关联的时间线,无法确定它们指令间的先后关系。JMM通过happens-before规则打破了这种隔离性,强制要求某些操作必须对其他线程可见,从而建立起逻辑上的先后顺序。

相关推荐
钢铁男儿24 分钟前
C# 类和继承(使用基类的引用)
java·javascript·c#
linweidong3 小时前
Go开发简历优化指南
分布式·后端·golang·高并发·简历优化·go面试·后端面经
敢敢变成了憨憨4 小时前
java操作服务器文件(把解析过的文件迁移到历史文件夹地下)
java·服务器·python
苇柠4 小时前
Java补充(Java8新特性)(和IO都很重要)
java·开发语言·windows
Lin_XXiang4 小时前
java对接bacnet ip协议(跨网段方式)
java·物联网
白总Server4 小时前
C++语法架构解说
java·网络·c++·网络协议·架构·golang·scala
咖啡啡不加糖4 小时前
雪花算法:分布式ID生成的优雅解决方案
java·分布式·后端
小杜-coding5 小时前
天机学堂(初始项目)
java·linux·运维·服务器·spring boot·spring·spring cloud
钢铁男儿5 小时前
深入剖析C#构造函数执行:基类调用、初始化顺序与访问控制
java·数据库·c#
姑苏洛言5 小时前
基于微信公众号小程序的课表管理平台设计与实现
前端·后端