一篇文章深入聊聊Java的内存模型
作者:晔子yy
1、Java的内存模型(JMM)介绍
JMM核心定义和作用
Java内存模型(Java Memory Model,JMM)是Java虚拟机规范中定义的一种抽象概念,它规定了多线程环境下,线程如何与内存进行交互。
JMM的核心作用:
定义程序中各个变量的访问规则
确保多线程程序的可见性、有序性和原子性
屏蔽不同硬件平台和操作系统的内存访问差异
JVM和JMM的区别
说到JMM,我们不得不提到它经常被人所搞混淆的另一个概念JVM,我们用一张表来直观表现出它们的区别。
JVM内存结构 | Java内存模型 | |
| 核心关注点 | 数据存储的物理/逻辑分区 | 线程与内存的交互规则 |
| 内容 | 堆、栈、方法区等内存区域 | 主内存、工作内存抽象概念 |
| 目的 | 内存分配与管理 | 多线程内存可见性控制 |
2.JMM核心概念
主内存和工作内存
主内存:所有线程共享的内存区域,存储所有实例字段、静态字段和数组元素
工作内存:每个线程私有的内存空间,存储线程使用变量的副本
当某个线程需要使用到内存中的变量时,他会先从主内存中复制一份该变量的副本到自己的工作内存当中,使用完后再将该变量写入主内存的共享内存中。

内存间的交互操作
lock/unlock:作用于主内存,标识变量为线程独占状态
read/load:从主内存读取变量到工作内存
use/assign:工作内存中使用和赋值操作
store/write:将工作内存变量写回主内存
内存三大特性
原子性
核心概念:原子性指一个操作或一系列操作要么全部执行成功,要么全部不执行,不会出现执行到一半的状态。
// 原子操作示例
int x = 10; // 原子的:一次性赋值
boolean flag = true; // 原子的
// 非原子操作示例
int i = 0;
i++; // 非原子的,实际包含3个步骤:
// 1.读取i的值到寄存器
// 2.寄存器值加1
// 3.写回内存 可见性
核心概念:可见性指当一个线程修改了共享变量的值,其他线程能够立即看到修改后的值。
private boolean running = true; // 主内存中的变量
public void run() {
while (running) { // 工作内存中的副本
// 看不到running被改为false
}
}
public void stop() {
running = false; // 修改主内存,但工作内存可能没更新
}有序性
核心概念:有序性指程序执行的顺序按照代码的先后顺序执行。但在多线程或优化环境下,指令可能被重排序。
重排序原因:
编译器优化重排序
处理器指令级并行重排序
内存系统重排序
private int x = 0;
private boolean flag = false;
// 线程1执行
public void writer() {
x = 42; // 1
flag = true; // 2 可能被重排到1之前!
}
// 线程2执行
public void reader() {
if (flag) { // 3
System.out.println(x); // 可能输出0而不是42!
}
}3.Happens-Before规则
Happens-Before规则介绍
Happens-Before是JMM的核心概念,它定义了两个操作之间的偏序关系:
如果操作A happens-before 操作B
那么A的所有写操作对B的读操作都是可见的
有点难看懂,我们用一个简单的例子就能快速理解
// 核心:happens-before ≠ 时间先后 int x = 0; int y = 0; // 时间上:先执行1,后执行2 x = 1; // 1 y = x + 1; // 2 // 逻辑上:1 happens-before 2 // 所以2一定能看到1写入的值
A happens-before B 翻译过来就是:A对B可见。
六大happens-before规则
- 程序次序规则:线程内,按照程序代码顺序,前面的操作happens-before后面的操作。
- 监视器锁规则:对一个锁的解锁操作happens-before后续对这个锁的加锁操作。
- volatile变量规则:对一个volatile变量的写操作happens-before后续对这个变量的读操作。
- 线程启动规则:Thread对象的start()方法调用happens-before该线程的每一个动作。
- 线程终止规则:线程中的所有操作happens-before其他线程检测到该线程已经终止
- 传递性规则:如果A happens-before B,且B happens-before C,那么A happens-before C
4.volatile关键字
核心概述:volatile是一个重要的关键字,用于告知编译器某个变量的值可能会被程序外部的因素意外修改,从而避免编译器对该变量进行优化。它的主要作用是确保每次访问变量时都从内存中读取最新的值,而不是使用寄存器中的缓存值。
volatile提供了两大保证:
- 可见性:修改立即对所有线程可见
- 有序性:禁止指令重排序
private volatile boolean flag = false;
private int count = 0;
public void writer() {
count = 42; // 普通写操作
flag = true; // volatile写操作
}
public void reader() {
if (flag) { // volatile读操作
// 这里一定能看到count=42
System.out.println(count);
}
}5.JMM的常见误区
volatile无法保证原子性
volatile可以保证可见性和有序性,但和synchronized不一样,不能保证原子性
// 错误:以为volatile能保证原子性 volatile int count = 0; count++; // 非原子操作 // 正确:使用原子类或同步 AtomicInteger atomicCount = new AtomicInteger(0); atomicCount.incrementAndGet();
指令重排序的陷阱
// 可能由于重排序导致问题
int a = 0;
boolean flag = false;
// 线程1
a = 1; // 1
flag = true; // 2 可能重排到1之前
// 线程2
if (flag) {
System.out.println(a); // 可能输出0
}此时我们需对flag使用volatile关键字修饰即可保证在a赋值后再执行flag=true操作。
总结
到此这篇关于Java内存模型的文章就介绍到这了,更多相关Java内存模型内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
