news 2026/2/17 4:09:33

Java内存模型(JMM)一文透彻理解

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Java内存模型(JMM)一文透彻理解

JMM核心内容概览与重要程度评级

在学习JMM前,我们先了解其核心内容体系及重要程度:

内容模块 重要程度 说明

1. JMM基础概念 ⭐⭐⭐⭐ 理解JMM的出发点和基本架构

- 硬件基础与并发挑战 ⭐⭐⭐⭐ 了解JMM存在的必要性

- 主内存与工作内存 ⭐⭐⭐⭐ JMM的核心抽象概念

2. 内存间交互操作 ⭐⭐⭐ JMM的基础操作定义

3. volatile关键字 ⭐⭐⭐⭐⭐ 最常用的同步机制,必须深入掌握

4. synchronized内存语义 ⭐⭐⭐⭐⭐ 理解锁的内存效应

5. happens-before规则 ⭐⭐⭐⭐⭐ JMM的理论核心,解决可见性问题的关键

6. 原子性、可见性、有序性 ⭐⭐⭐⭐⭐ 并发编程的三大核心问题

7. 安全发布模式 ⭐⭐⭐⭐ 实际开发中的常用技巧

8. final字段语义 ⭐⭐⭐ 特殊但重要的内存语义

9. 双重检查锁定问题 ⭐⭐⭐⭐ 经典问题的分析与解决方案

10. JMM底层实现 ⭐⭐⭐ 理解原理,优化性能

接下来,我们将按照重要程度,逐一深入讲解各个模块。

1. JMM是什么?为什么需要JMM?

1.1 JMM的定义与作用

Java内存模型(Java Memory Model, JMM) 是Java虚拟机规范中定义的一种抽象规范,用于屏蔽各种硬件和操作系统的内存访问差异,实现Java程序在各种平台下都能达到一致的内存访问效果。

JMM的核心作用:

定义规则:规定多线程环境下变量的访问方式

提供保证:确保在不同平台上内存访问行为的一致性

允许优化:在保证正确性的前提下允许编译器和处理器进行优化

1.2 为什么需要JMM:硬件层面的挑战

现代计算机系统的多层次存储架构导致了并发编程的三大核心问题:

public class ConcurrencyProblems {

private static boolean ready = false;

private static int number = 0;

public static void main(String[] args) {

// 线程1:数据准备

Thread writer = new Thread(() -> {

number = 42; // 操作1:可能被重排序到操作2之后

ready = true; // 操作2:可能先执行

});

// 线程2:数据处理

Thread reader = new Thread(() -> {

while (!ready) {

// 等待ready变为true

Thread.yield();

}

// 可能输出0而不是42!

System.out.println("Number: " + number);

});

writer.start();

reader.start();

}

}

问题根源:

CPU缓存一致性:多核CPU各有缓存,数据更新不同步

指令重排序:编译器和处理器为优化性能重新排序指令

内存可见性:一个线程的修改对其他线程不可见

2. JMM的核心架构:主内存与工作内存

JMM通过抽象的内存模型解决上述问题:

物理硬件对应关系

CPU Core 1

缓存 L1/L2

CPU Core 2

缓存 L1/L2

共享缓存 L3

主内存 RAM

JMM内存模型

线程2

线程1

read/load

store/write

read/load

store/write

工作内存

主内存变量副本

工作内存

主内存变量副本

执行引擎

执行引擎

主内存 Main Memory

共享变量存储区域

工作内存与主内存的交互通过8种原子操作完成:

public class MemoryOperations {

private int sharedValue = 0;

public void operationExample() {

// 1. read: 从主内存读取变量到传输通道

// 2. load: 将read得到的值放入工作内存的变量副本

// 相当于: int temp = sharedValue; (但这是高级语言表示)

// 3. use: 将工作内存中的变量传递给执行引擎

int result = sharedValue * 2;

// 4. assign: 将执行引擎的结果赋给工作内存中的变量

sharedValue = result + 1;

// 5. store: 将工作内存中的变量值传输到主内存的传输通道

// 6. write: 将store获取的值放入主内存的变量

// 7. lock: 将主内存变量标记为线程独占状态

// 8. unlock: 释放锁定的变量

}

}

3. ⭐⭐⭐⭐⭐ volatile关键字深度解析

3.1 volatile的语义与保证

volatile是JVM提供的最轻量级的同步机制,提供两大保证:

可见性保证:对volatile变量的写操作立即对其他线程可见

禁止重排序:阻止编译器和处理器对volatile操作进行重排序

public class VolatileExample {

private volatile boolean flag = false;

private int value = 0;

public void writer() {

value = 42; // 普通写操作

// StoreStore内存屏障:禁止上面的普通写与下面的volatile写重排序

flag = true; // volatile写操作

// StoreLoad内存屏障:确保volatile写立即对其他处理器可见

}

public void reader() {

// LoadLoad内存屏障:确保volatile读之前的所有读操作已完成

if (flag) { // volatile读操作

// LoadStore内存屏障:确保volatile读之后的写操作不会重排序到读之前

System.out.println(value); // 保证看到value = 42

}

}

}

3.2 volatile的实现原理

在硬件层面,volatile通过内存屏障指令实现:

public class VolatileBarrier {

private volatile int value;

public void setValue(int newValue) {

this.value = newValue;

// 对应x86汇编代码:

// mov %eax,0x10(%rsi) ; 将newValue存入value的内存地址

// lock addl $0x0,(%rsp) ; StoreLoad内存屏障(mfence指令)

}

public int getValue() {

// volatile读在x86上不需要特殊指令

// 因为x86的内存模型已经保证了可见性(TSO模型)

return value;

}

}

内存屏障类型:

LoadLoad屏障:禁止读操作重排序

StoreStore屏障:禁止写操作重排序

LoadStore屏障:禁止读与写操作重排序

StoreLoad屏障:禁止写与读操作重排序(最重量级)

3.3 volatile的使用场景与限制

适用场景:

状态标志位

一次性安全发布

独立观察(independent observation)

开销较低的读-写锁策略

不适用场景:

复合操作(如i++)

依赖于当前值的操作(如value = value + 1)

public class VolatileUsage {

// 场景1:状态标志位

private volatile boolean shutdownRequested;

public void shutdown() {

shutdownRequested = true;

}

public void doWork() {

while (!shutdownRequested) {

// 执行工作任务

}

}

// 场景2:一次性安全发布

private volatile Resource resource;

public Resource getResource() {

if (resource == null) {

synchronized(this) {

if (resource == null) {

resource = new Resource(); // 安全发布

}

}

}

return resource;

}

}

4. ⭐⭐⭐⭐⭐ synchronized的内存语义

synchronized不仅提供互斥执行,还提供重要的内存语义:

public class SynchronizedMemory {

private int counter = 0;

private final Object lock = new Object();

public void increment() {

synchronized(lock) {

// monitorenter指令:

// 1. 清空工作内存

// 2. 从主内存重新加载所有共享变量

counter++;

// 临界区内的操作不会被重排序到临界区外

}

// monitorexit指令:

// 1. 将工作内存中的修改刷新到主内存

// 2. 释放锁

}

public int getCounter() {

synchronized(lock) {

// 获取锁会强制从主内存重新读取变量

return counter;

}

}

}

synchronized的内存语义:

进入同步块:清空工作内存,从主内存重新加载变量

退出同步块:将工作内存中的修改刷新到主内存

互斥执行:确保同一时刻只有一个线程执行临界区代码

5. ⭐⭐⭐⭐⭐ happens-before规则

happens-before是JMM的理论核心,定义了操作之间的可见性关系。

5.1 happens-before规则详解

public class HappensBeforeExample {

private int x = 0;

private volatile boolean v = false;

private int y = 0;

private final Object lock = new Object();

public void demo() {

// 规则1:程序次序规则

x = 1; // 操作A

v = true; // 操作B:A happens-before B

// 规则2:volatile变量规则

if (v) { // 操作C:B happens-before C

y = x; // 操作D:C happens-before D

}

// 规则3:传递性规则

// A happens-before B, B happens-before C, C happens-before D

// 因此 A happens-before D

// 规则4:管程锁定规则

synchronized(lock) { // 加锁E

x = 2; // 操作F:E happens-before F

} // 解锁G:F happens-before G

// 规则5:线程启动规则

Thread t = new Thread(() -> {

System.out.println(x); // 看到x=2

});

t.start(); // start() happens-before 线程中的所有操作

// 规则6:线程终止规则

try {

t.join(); // 线程中的所有操作 happens-before join()返回

} catch (InterruptedException e) {

Thread.currentThread().interrupt();

}

// 规则7:对象终结规则

// 对象的构造函数执行结束 happens-before finalize()方法开始

}

}

5.2 happens-before的数学基础

happens-before关系是一个偏序关系,具有:

自反性:A happens-before A

反对称性:如果A happens-before B且B happens-before A,则A=B

传递性:如果A happens-before B且B happens-before C,则A happens-before C

6. ⭐⭐⭐⭐⭐ 原子性、可见性、有序性

这是并发编程的三大核心问题,JMM为每个问题提供了解决方案。

6.1 原子性(Atomicity)

原子性是指一个操作不可中断,要么全部执行成功,要么完全不执行。

public class AtomicityExample {

private int basicType = 0; // 基本类型访问是原子的

private long longValue = 0L; // long和double可能非原子(但现代JVM通常保证原子性)

private volatile boolean flag = false; // volatile保证单个读/写的原子性

// 复合操作不是原子的

public void nonAtomicIncrement() {

basicType++; // 不是原子操作!分解为read-modify-write三步

}

// 保证原子性的方式

private final AtomicInteger atomicInt = new AtomicInteger(0);

private final Object lock = new Object();

private int synchronizedValue = 0;

public void atomicOperations() {

// 方式1:使用原子类

atomicInt.incrementAndGet(); // 原子操作

// 方式2:使用同步

synchronized(lock) {

synchronizedValue++; // 原子操作

}

// 方式3:使用volatile变量(仅适用于特定场景)

flag = true; // 原子操作

}

}

6.2 可见性(Visibility)

可见性是指当一个线程修改了共享变量的值,其他线程能够立即得知这个修改。

public class VisibilityExample {

private int noVisibility = 0; // 无可见性保证

private volatile boolean hasVisibility = false; // volatile保证可见性

private int synchronizedValue = 0; // synchronized保证可见性

private final Object lock = new Object();

public void demonstrate() {

// 线程1:修改数据

new Thread(() -> {

noVisibility = 42;

hasVisibility = true;

synchronized(lock) {

synchronizedValue = 100;

}

}).start();

// 线程2:读取数据

new Thread(() -> {

// 可能看不到noVisibility的更新

while (!hasVisibility) {

// 等待hasVisibility变为true

}

// 保证看到noVisibility = 42(因为volatile写happens-before volatile读)

synchronized(lock) {

// 保证看到synchronizedValue = 100

}

}).start();

}

}

6.3 有序性(Ordering)

有序性是指程序执行的顺序按照代码的先后顺序执行。

public class OrderingExample {

private int x = 0;

private int y = 0;

private volatile boolean ready = false;

public void orderingDemo() {

// 线程1:可能被重排序

new Thread(() -> {

x = 1; // 操作1

y = 2; // 操作2

ready = true; // 操作3:volatile写,阻止重排序

}).start();

// 线程2

new Thread(() -> {

while (!ready) {

// 等待

}

// 由于volatile的语义,这里保证看到x=1, y=2

// 不会出现y=2但x=0的情况

}).start();

}

}

7. ⭐⭐⭐⭐ 安全发布模式

安全地发布对象是并发编程中的常见需求,JMM提供了多种模式。

public class SafePublication {

// 方式1:静态初始化器(最安全)

private static final Resource staticResource = new Resource();

// 方式2:volatile字段

private volatile Resource volatileResource;

public void initVolatileResource() {

volatileResource = new Resource(); // 安全发布

}

// 方式3:final字段

private final Resource finalResource;

public SafePublication() {

this.finalResource = new Resource(); // 安全发布

}

// 方式4:正常锁保护

private Resource guardedResource;

private final Object lock = new Object();

public void initGuardedResource() {

synchronized(lock) {

if (guardedResource == null) {

guardedResource = new Resource(); // 安全发布

}

}

}

// 方式5:线程安全容器

private final Map<String, Resource> safeMap

= Collections.synchronizedMap(new HashMap<>());

private final ConcurrentMap<String, Resource> concurrentMap

= new ConcurrentHashMap<>();

public void addToSafeMap(String key) {

safeMap.put(key, new Resource()); // 安全发布

}

}

8. ⭐⭐⭐⭐ 双重检查锁定(DCL)问题与解决方案

双重检查锁定是一个经典的并发模式,但存在陷阱。

8.1 错误的DCL实现

public class BrokenDCL {

private static Resource resource; // 没有volatile!

public static Resource getInstance() {

if (resource == null) { // 第一次检查(无锁)

synchronized(BrokenDCL.class) { // 加锁

if (resource == null) { // 第二次检查(有锁)

resource = new Resource(); // 问题所在!

// 可能发生的重排序:

// 1. 分配内存空间

// 2. 将引用指向内存空间(此时resource != null)

// 3. 初始化对象(还未执行)

// 其他线程可能拿到未完全初始化的对象!

}

}

}

return resource;

}

}

8.2 正确的DCL实现

public class CorrectDCL {

// 使用volatile禁止重排序

private static volatile CorrectDCL instance;

private final int value;

private final String name;

private CorrectDCL() {

this.value = 42; // 初始化final字段

this.name = "DCL"; // 初始化普通字段

// 构造函数执行

}

public static CorrectDCL getInstance() {

if (instance == null) { // 第一次检查(无锁)

synchronized(CorrectDCL.class) { // 加锁

if (instance == null) { // 第二次检查(有锁)

instance = new CorrectDCL(); // 安全发布

// volatile写插入内存屏障,确保:

// 1. 所有初始化操作完成

// 2. 初始化结果对其他线程立即可见

}

}

}

return instance;

}

}

9. ⭐⭐⭐ final字段的内存语义

final字段在并发编程中有特殊的内存语义,提供了安全初始化的保证。

public class FinalFieldExample {

private final int finalValue; // final字段

private int normalValue; // 普通字段

private volatile boolean ready = false;

public FinalFieldExample() {

normalValue = 1; // 普通字段写入(可能被重排序)

finalValue = 42; // final字段写入

// JMM在此隐式插入StoreStore内存屏障

// 确保final字段的初始化不会被重排序到构造函数之外

ready = true; // volatile写

}

public static void reader() {

FinalFieldExample obj = new FinalFieldExample();

// 保证看到finalValue的正确值(42)

// 即使没有同步,也能看到正确初始化的final字段

int r1 = obj.finalValue;

// 可能看到normalValue的默认值(0)而不是1

// 因为没有同步保证

int r2 = obj.normalValue;

// 但如果通过volatile读看到ready=true

// 那么也能保证看到所有字段的正确初始化值

if (obj.ready) {

// 保证看到finalValue=42和normalValue=1

}

}

}

10. JMM在开发中的实际应用

10.1 性能优化建议

减少同步范围:只在必要时使用同步

使用volatile代替锁:当只需要可见性保证时

使用线程局部变量:避免共享,消除同步

使用并发容器:代替手动同步的容器

10.2 常见陷阱与避免方法

public class CommonConcurrencyMistakes {

// 陷阱1:认为volatile保证原子性

private volatile int count = 0;

public void unsafeIncrement() {

count++; // 不是原子操作!

}

// 解决方案:使用原子类或同步

private final AtomicInteger safeCount = new AtomicInteger(0);

private int synchronizedCount = 0;

private final Object lock = new Object();

public void safeIncrement() {

safeCount.incrementAndGet(); // 方式1:原子类

synchronized(lock) { // 方式2:同步

synchronizedCount++;

}

}

// 陷阱2:误用双重检查锁定

// 解决方案:使用volatile修饰实例变量

// 陷阱3:依赖线程优先级

// 解决方案:不要依赖线程优先级进行正确性设计

// 陷阱4:在构造函数中启动线程

public class ProblematicConstructor {

public ProblematicConstructor() {

new Thread(() -> {

// 可能访问未完全初始化的对象

}).start();

}

}

}

总结

Java内存模型是Java并发编程的基石,它通过定义一系列规则和happens-before关系,在多线程环境中提供了内存可见性、原子性和有序性的保证。

关键要点:

理解happens-before规则:这是理解线程间操作可见性的核心

正确使用volatile:了解其适用场景和限制

掌握安全发布模式:确保对象在线程间安全共享

避免常见陷阱:识别并避免常见的并发编程错误

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/2/17 10:30:21

机械臂自适应神经网络控制,机械臂为三自由度,神经网络逼近系统的动力学和滞回非线性

机械臂自适应神经网络控制&#xff0c;机械臂为三自由度&#xff0c;神经网络逼近系统的动力学和滞回非线性。 利用径向基函数的神经网络近似机器人的动力学。 对于系统状态未知的输出反馈&#xff0c;采用高增益观测器估计系统状态。 在工业机器人控制领域&#xff0c;三自由…

作者头像 李华
网站建设 2026/1/29 10:37:17

【JavaWeb】Servlet_生命周期

目录生命周期简介什么是Servlet的生命周期Servlet容器Servlet主要的生命周期执行特点如何让Servlet对象在Tomcat启动时就实例化DefaultServlet生命周期简介 什么是Servlet的生命周期 应用程序中的对象不仅在空间上有层次结构的关系&#xff0c;在时间上也会因为处于程序运行过…

作者头像 李华
网站建设 2026/2/8 12:03:32

【Java毕设源码分享】基于springboot+vue的隔离人员的管理系统设计与实现(程序+文档+代码讲解+一条龙定制)

博主介绍&#xff1a;✌️码农一枚 &#xff0c;专注于大学生项目实战开发、讲解和毕业&#x1f6a2;文撰写修改等。全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围&#xff1a;&am…

作者头像 李华
网站建设 2026/2/16 18:26:45

【Java毕设源码分享】基于springboot+vue的高校网上订餐平台的设计与实现_(程序+文档+代码讲解+一条龙定制)

博主介绍&#xff1a;✌️码农一枚 &#xff0c;专注于大学生项目实战开发、讲解和毕业&#x1f6a2;文撰写修改等。全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围&#xff1a;&am…

作者头像 李华
网站建设 2026/2/13 10:06:24

DAY25 pipeline管道

浙大疏锦行 # 导入基础库 import pandas as pd import numpy as np import matplotlib.pyplot as plt import seaborn as sns import time # 导入 time 库 import warnings# 忽略警告 warnings.filterwarnings("ignore")# 设置中文字体和负号正常显示 plt.rcParams[…

作者头像 李华