温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

JMM内存模型是什么

发布时间:2022-02-19 10:02:19 来源:亿速云 阅读:169 作者:iii 栏目:开发技术
# JMM内存模型是什么 ## 引言 在多线程编程的世界中,**Java内存模型(Java Memory Model, JMM)**是确保程序正确性的核心理论基础。随着多核处理器成为现代计算机的标配,理解JMM对于编写高效、线程安全的Java程序至关重要。本文将深入剖析JMM的定义、核心概念、实现原理以及实际应用场景,帮助开发者跨越"可见性"与"有序性"的认知鸿沟。 ## 一、JMM的定义与背景 ### 1.1 什么是内存模型 内存模型(Memory Model)是计算机科学中描述**多线程环境下内存访问行为**的规范。它定义了: - 线程如何通过内存进行交互 - 内存操作(读/写)的可见性规则 - 指令重排序的约束条件 ### 1.2 JMM的诞生背景 Java最初的内存模型(Java 1.0-1.4)存在严重缺陷: - 无法有效禁止编译器和处理器的优化重排序 - final字段的线程安全性无法保证 - volatile的语义不够严格 2004年JSR-133(Java内存模型与线程规范)彻底重构了JMM,并在Java 5.0中正式发布。 ### 1.3 JMM的核心目标 1. **平台无关性**:在不同硬件架构上保持一致的并发语义 2. **性能优化空间**:允许编译器和处理器进行合理的优化 3. **程序员友好**:提供清晰的可预测行为 ## 二、JMM的核心概念 ### 2.1 主内存与工作内存 JMM将内存抽象为两层结构: | 内存类型 | 存储内容 | 特性 | |------------|------------------------------|--------------------------| | 主内存 | 实例字段、静态字段、数组元素 | 线程共享 | | 工作内存 | 方法参数、局部变量 | 线程私有,存在可见性问题 | ```java // 示例:共享变量与局部变量 class Example { static int sharedVar; // 主内存 void method() { int localVar = 0; // 工作内存 } } 

2.2 内存间交互操作

JMM定义了8种原子性内存操作(以下均为JVM内部操作):

  1. lock(锁定):作用于主内存变量
  2. unlock(解锁):作用于主内存变量
  3. read(读取):从主内存传输到工作内存
  4. load(载入):将read得到的值放入工作内存副本
  5. use(使用):执行引擎使用变量值
  6. assign(赋值):将新值赋给工作内存变量
  7. store(存储):将工作内存值传输到主内存
  8. write(写入):将store得到的值放入主内存变量

2.3 happens-before原则

JMM最关键的排序规则,定义6种天然happens-before关系:

  1. 程序顺序规则:同一线程内的操作按程序顺序发生
  2. 监视器锁规则:unlock操作先于后续的lock操作
  3. volatile规则:volatile写先于后续的读
  4. 线程启动规则:Thread.start()先于线程内任何操作
  5. 线程终止规则:线程中所有操作先于线程终止检测
  6. 传递性:若A hb B,B hb C,则A hb C
// happens-before示例 class HBExample { int x = 0; volatile boolean v = false; void writer() { x = 42; // 1 v = true; // 2 volatile写 } void reader() { if (v) { // 3 volatile读 System.out.println(x); // 保证看到42 } } } 

三、JMM的三大特性

3.1 原子性(Atomicity)

JMM保证以下操作的原子性: - 基本类型(除long/double)的读写 - volatile修饰的long/double的读写 - synchronized块内的操作

// 非原子操作示例 class AtomicityExample { long counter = 0L; // 64位非volatile变量 void increment() { counter++; // 非原子操作(实际是read-modify-write三步) } } 

3.2 可见性(Visibility)

保证一个线程修改共享变量后,其他线程能立即看到:

实现方式 原理
volatile 禁止缓存,直接读写主内存
synchronized unlock前必须同步到主内存
final 正确初始化后对其他线程可见
// 可见性问题示例 class VisibilityIssue { boolean ready = false; // 非volatile int result = 0; void writer() { result = 42; // 可能重排序到ready=true之后 ready = true; } void reader() { if (ready) { // 可能看到ready为true但result仍为0 System.out.println(result); } } } 

3.3 有序性(Ordering)

禁止特定类型的指令重排序:

场景 允许重排序 禁止重排序
普通读写操作 ×
volatile读/写 × 建立内存屏障
synchronized块 × 建立全内存屏障

四、JMM的实现机制

4.1 内存屏障(Memory Barrier)

JVM插入的底层指令,分为4种类型:

屏障类型 作用 对应Java关键字
LoadLoad 禁止读-读重排序 volatile读
StoreStore 禁止写-写重排序 volatile写
LoadStore 禁止读-写重排序 volatile读
StoreLoad 禁止写-读重排序(全能屏障) volatile写

4.2 volatile的实现细节

volatile变量的特殊处理: 1. 写操作后插入StoreStore + StoreLoad屏障 2. 读操作前插入LoadLoad + LoadStore屏障

class VolatileExample { volatile int v = 0; int normal = 0; void write() { normal = 1; // 可能被重排序 v = 2; // 写屏障 } void read() { if (v == 2) { // 读屏障 System.out.println(normal); // 保证看到1 } } } 

4.3 final字段的特殊处理

JSR-133增强的final语义: 1. 构造函数内对final域的写入禁止重排序到构造函数外 2. 初次读取包含final域的对象引用时,保证看到所有final域的初始化值

class FinalExample { final int x; int y; static FinalExample instance; public FinalExample() { x = 1; // 保证在构造函数完成前写入 y = 2; // 普通写入可能重排序 } void writer() { instance = new FinalExample(); } void reader() { if (instance != null) { int i = instance.x; // 保证看到1 int j = instance.y; // 可能看到0 } } } 

五、JMM与硬件内存模型

5.1 不同架构的差异

架构 内存模型特性 JMM适配策略
x86 强内存模型,StoreLoad开销大 减少StoreLoad屏障使用
ARM/POWER 弱内存模型,允许更多重排序 插入更多内存屏障
SPARC TSO(全存储定序) 类似x86的处理方式

5.2 MESI缓存一致性协议

现代CPU通过MESI协议保证缓存一致性: - Modified:缓存行已被修改 - Exclusive:缓存行独占 - Shared:缓存行共享 - Invalid:缓存行无效

JMM在MESI基础上增加了: 1. 写缓冲区(Write Buffer)处理 2. 无效队列(Invalidation Queue)优化

六、JMM实践指南

6.1 正确使用volatile

适用场景: 1. 状态标志位(如shutdown请求) 2. 一次性安全发布(如双重检查锁定) 3. 独立观察(如定期统计上报)

// 典型volatile使用场景 class VolatileUsage { volatile boolean shutdownRequested; void shutdown() { shutdownRequested = true; } void doWork() { while (!shutdownRequested) { // 执行任务 } } } 

6.2 安全发布模式

  1. 静态初始化:利用类加载机制
     public static Holder holder = new Holder(42); 
  2. volatile引用
     volatile Holder holder; 
  3. final字段
     class Holder { final int n; Holder(int n) { this.n = n; } } 

6.3 避免常见误区

  1. 误认为volatile保证原子性

    volatile int count = 0; count++; // 仍然不是原子操作! 
  2. 过度依赖线程调度

    while (!flag) {} // 忙等待,应使用wait/notify 

七、JMM性能优化

7.1 减少争用

  1. 缩小同步块范围: “`java // 反例 synchronized(this) { // 大量非共享操作 sharedVar++; }

// 正例 int temp; // 非共享操作… synchronized(this) { temp = ++sharedVar; }

 2. **使用线程本地变量**: ```java ThreadLocal<SimpleDateFormat> dateFormat = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd")); 

7.2 避免伪共享

使用@Contended注解(Java 8+):

class ContendedValue { @sun.misc.Contended volatile long value1; @sun.misc.Contended volatile long value2; } 

八、JMM与其他内存模型对比

8.1 与C++11内存模型对比

特性 Java内存模型 C++11内存模型
原子操作 通过volatile和Atomic类 atomic模板类
顺序一致性 需要显式同步 memory_order_seq_cst
最弱约束 程序顺序规则 memory_order_relaxed

8.2 与Go内存模型对比

Go的happens-before规则更简单: - 仅通过channel通信建立happens-before关系 - 没有显式的volatile关键字 - sync包提供类似Java的同步原语

九、JMM的未来发展

  1. Project Loom:轻量级线程对内存模型的影响
  2. Valhalla项目:值类型带来的内存模型变化
  3. 异构计算:GPU/FPGA等设备的内存一致性挑战

结语

理解Java内存模型是成为高级Java开发者的必经之路。通过本文的系统性梳理,我们不仅掌握了JMM的理论基础,还学习了如何在实际开发中应用这些知识。记住:在并发编程中,“看起来正确”远远不够,必须”证明正确”。只有深入理解内存模型,才能编写出真正线程安全的Java程序。

参考资料

  1. JSR-133: Java Memory Model and Thread Specification
  2. 《Java并发编程实战》Brian Goetz 等著
  3. 《The JSR-133 Cookbook for Compiler Writers》
  4. OpenJDK HotSpot源码(orderAccess.hpp)

”`

注:本文实际约5800字(含代码示例),可根据需要调整具体示例的详细程度。建议读者结合Java语言规范和JVM规范进行延伸阅读。

向AI问一下细节

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

jmm
AI