Java内存模型(JMM)深度解析:从并发问题到解决方案
- 电脑硬件
- 2025-09-05 10:00:02

一、为什么需要内存模型(JMM)?
在多核CPU时代,并发编程是提升程序性能的核心手段,但是并发带来了三大经典问题:
可见性问题:线程A修改的变量,线程B无法立即看到
原子性问题:i++操作在机器指令层面可能被中断
有序性问题:代码执行顺序与编写顺序不一致
这些问题的根源在于:
CPU多级缓存架构导致内存可见性问题
编译器/处理器优化引发指令重排序
线程切换带来的原子性破坏
CPU Cache 示意图 二、JMM架构设计 2.1 内存抽象模型JMM定义了两个核心概念:
主内存(Main Memory):所有线程共享的内存区域
工作内存(Working Memory):每个线程私有的内存副本
从上图来看,线程 1 与线程 2 之间如果要进行通信的话,必须要经历下面 2 个步骤:
线程 1 把本地内存中修改过的共享变量副本的值同步到主内存中去。线程 2 到主存中读取对应的共享变量的值。也就是说,JMM 为共享变量提供了可见性的保障。
不过,多线程下,对主内存中的一个共享变量进行操作有可能诱发线程安全问题。举个例子:
线程 1 和线程 2 分别对同一个共享变量进行操作,一个执行修改,一个执行读取。线程 2 读取到的是线程 1 修改之前的值还是修改后的值并不确定,都有可能,因为线程 1 和线程 2 都是先将共享变量从主内存拷贝到对应线程的工作内存中。 2.2 内存交互协议JMM定义了8种原子操作保证内存可见性:
操作作用范围说明lock主内存变量标记变量为线程独占状态unlock主内存变量释放变量的锁定状态read主内存变量将变量值传输到线程工作区load工作内存将read的值放入变量副本use工作内存将变量值传递给执行引擎assign工作内存将执行结果赋值给变量副本store工作内存将变量值传送到主内存write主内存变量将store的值放入主内存变量 三、happens-before原则 3.1 核心规则程序顺序规则:单线程内的操作按代码顺序保证有序性
volatile规则:volatile写操作先于后续的读操作
锁规则:解锁操作先于后续的加锁操作
传递性规则:A→B且B→C,则A→C
线程启动规则:Thread.start()先于线程内所有操作
下面这张是 《Java 并发编程的艺术》这本书中的一张 JMM 设计思想的示意图:
3.2 实际案例 // 示例:双重检查锁定单例模式 public class Singleton { private volatile static Singleton instance; public static Singleton getInstance() { if (instance == null) { // 第一次检查 synchronized (Singleton.class) { // 加锁 if (instance == null) { // 第二次检查 instance = new Singleton(); // volatile写 } } } return instance; } }这里volatile的happens-before关系保证了:
对象初始化完成 → 写操作
写操作 → 读操作
四、内存屏障与指令重排序 4.1 屏障类型 屏障类型作用LoadLoad屏障禁止读操作重排序StoreStore屏障禁止写操作重排序LoadStore屏障禁止读后写重排序StoreLoad屏障禁止写后读重排序(全能屏障) 4.2 volatile实现原理 public class VolatileExample { private volatile int flag = 0; public void writer() { flag = 1; // StoreStore屏障 + StoreLoad屏障 } public void reader() { if (flag == 1) { // LoadLoad屏障 + LoadStore屏障 // do something } } }volatile变量的读写会插入内存屏障:
写操作前插入StoreStore屏障
写操作后插入StoreLoad屏障
读操作前插入LoadLoad屏障
读操作后插入LoadStore屏障
五、JMM与JVM内存结构对比 特性JMMJVM内存结构关注点多线程内存可见性问题内存区域划分与管理核心概念主内存、工作内存堆、栈、方法区等规范级别语言级内存模型虚拟机实现规范可见性保证通过happens-before规则不直接处理可见性问题典型应用volatile、synchronized语义对象分配、垃圾回收 六、并发问题解决方案 6.1 可见性问题可见性问题概述:在多线程环境中,每个线程都有自己的工作内存,线程对变量的操作是先从主内存拷贝到工作内存,操作完成后再写回主内存。这就可能导致一个线程对变量的修改,其他线程不能及时看到,从而引发可见性问题。
volatile关键字:强制所有读写直接操作主内存
public class VolatileVisibilityExample { private volatile boolean flag = false; public void writer() { flag = true; // 写操作直接更新主内存 } public void reader() { while (!flag) { // 等待 flag 变为 true } // 由于 flag 是 volatile 变量,能及时看到 writer 线程对 flag 的修改 System.out.println("Flag is now true"); } }synchronized同步块:解锁时自动刷新工作内存到主内存
public class SynchronizedVisibilityExample { private boolean flag = false; private final Object lock = new Object(); public void writer() { synchronized (lock) { flag = true; // 修改共享变量 } // 退出同步块,将修改刷新到主内存 } public void reader() { synchronized (lock) { // 进入同步块,从主内存读取最新的 flag 值 if (flag) { System.out.println("Flag is true"); } } } }final关键字:正确发布的不可变对象保证可见性
public class FinalVisibilityExample { private final int value; public FinalVisibilityExample(int value) { this.value = value; // 初始化 final 变量 } public int getValue() { return value; } }6.2 原子性问题
原子性问题概述:原子性是指一个操作或一系列操作要么全部执行,要么全部不执行,不会被其他线程中断。在多线程环境中,如果多个线程同时对一个共享变量进行读写操作,可能会导致数据不一致的问题,因为这些操作可能不是原子性的。
Atomic原子类:基于CAS实现无锁编程
import java.util.concurrent.atomic.AtomicInteger; public class AtomicExample { private AtomicInteger counter = new AtomicInteger(0); public void increment() { counter.incrementAndGet(); // 原子性自增操作 } public int getCounter() { return counter.get(); } }synchronized同步:通过互斥保证原子性
public class SynchronizedAtomicExample { private int counter = 0; public synchronized void increment() { counter++; // 同步方法,保证原子性 } public synchronized int getCounter() { return counter; } }Lock接口实现:显式锁控制临界区
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class LockAtomicExample { private int counter = 0; private final Lock lock = new ReentrantLock(); public void increment() { lock.lock(); try { counter++; // 临界区,保证原子性 } finally { lock.unlock(); } } public int getCounter() { lock.lock(); try { return counter; } finally { lock.unlock(); } } }6.3 有序性问题
有序性问题概述:在多线程环境中,编译器和处理器为了提高性能,可能会对指令进行重排序。重排序可能会导致程序的执行顺序与代码的编写顺序不一致,从而引发有序性问题。
volatile:禁止指令重排序
public class VolatileOrderingExample { private int a = 0; private volatile boolean flag = false; public void writer() { a = 1; // 操作 1 flag = true; // 操作 2,由于 flag 是 volatile 变量,操作 1 不会重排序到操作 2 之后 } public void reader() { if (flag) { // 操作 3 int i = a; // 操作 4,操作 4 不会重排序到操作 3 之前 } } }synchronized:保证临界区内代码串行执行
public class SynchronizedOrderingExample { private int a = 0; private int b = 0; private final Object lock = new Object(); public void writer() { synchronized (lock) { a = 1; // 操作 1 b = 2; // 操作 2,操作 1 和操作 2 会按顺序执行 } } public void reader() { synchronized (lock) { int x = b; // 操作 3 int y = a; // 操作 4,操作 3 和操作 4 会按顺序执行 } } }final:正确构造的对象保证初始化安全
public class FinalOrderingExample { private final int value; public FinalOrderingExample(int value) { this.value = value; // 正确初始化 final 字段 } public int getValue() { return value; } } 七、实战:诊断内存可见性问题 public class VisibilityDemo { boolean ready = false; int result = 0; int number = 1; public void write() { number = 2; // 操作1 ready = true; // 操作2 } public void read() { if (ready) { // 操作3 result = number; // 操作4 } } }可能出现的结果:
线程A执行write()
线程B执行read()可能得到result=1(指令重排序导致操作2先于操作1)
解决方案:
将ready声明为volatile
使用synchronized同步方法
Java内存模型(JMM)深度解析:从并发问题到解决方案由讯客互联电脑硬件栏目发布,感谢您对讯客互联的认可,以及对我们原创作品以及文章的青睐,非常欢迎各位朋友分享到个人网站或者朋友圈,但转载请说明文章出处“Java内存模型(JMM)深度解析:从并发问题到解决方案”