Java | Java内存模型JMM

引言

在多线程编程中,可见性原子性有序性是三个核心问题。Java内存模型(JMM)正是为了解决这些问题而设计的规范。它定义了Java程序在多线程环境下的内存访问规则,是并发编程的基石。本文将带你彻底搞懂JMM的核心概念、内存交互操作以及如何解决并发问题。

为什么要了解Java内存模型?

先看一段看似简单的代码:

java 复制代码
public class VisibilityDemo {
    private static boolean flag = true;
    
    public static void main(String[] args) throws InterruptedException {
        new Thread(() -> {
            while (flag) {
                // 忙等待
            }
            System.out.println("线程结束");
        }).start();
        
        Thread.sleep(1000);
        flag = false;
        System.out.println("主线程已将flag设为false");
    }
}

预期结果:线程会退出循环,输出"线程结束"

实际结果 :线程永远不会退出!因为子线程看不到主线程修改的flag值。

这就是可见性问题。JMM正是为了解决这类问题而存在的。

JMM内存模型架构图

JMM的核心概念

主内存 vs 工作内存

内存类型 归属 作用 类比
主内存 所有线程共享 存储所有共享变量 数据库
工作内存 每个线程私有 存储变量的副本 CPU缓存

核心规则

  • 所有变量都存储在主内存中
  • 每个线程有自己的工作内存,保存主内存变量的副本
  • 线程对变量的所有操作都必须在工作内存中进行
  • 线程间无法直接访问对方的工作内存
  • 线程间通信必须通过主内存中转

JMM的三大特性

原子性(Atomicity)

一个或多个操作要么全部执行且不被中断,要么全部不执行。

Java中的原子操作

  • 基本类型的读取和赋值是原子的(除了long和double)
  • 通过synchronizedLockAtomicXXX类保证原子性
java 复制代码
import java.util.concurrent.atomic.AtomicInteger;

public class AtomicityDemo {
    private static int count = 0;
    private static AtomicInteger atomicCount = new AtomicInteger(0);
    private static final int LOOP = 10000;
    
    public static void main(String[] args) throws InterruptedException {
        // 非原子操作演示
        Thread[] threads = new Thread[10];
        for (int i = 0; i < 10; i++) {
            threads[i] = new Thread(() -> {
                for (int j = 0; j < LOOP; j++) {
                    // 非原子操作:读-改-写
                    count++;  
                }
            });
            threads[i].start();
        }
        
        for (Thread t : threads) {
            t.join();
        }
        System.out.println("count++ 结果:" + count + ",预期:" + (10 * LOOP));
        
        // 原子操作演示
        for (int i = 0; i < 10; i++) {
            threads[i] = new Thread(() -> {
                for (int j = 0; j < LOOP; j++) {
                    // 原子操作
                    atomicCount.incrementAndGet();  
                }
            });
            threads[i].start();
        }
        
        for (Thread t : threads) {
            t.join();
        }
        System.out.println("atomicCount结果:" + atomicCount.get() + ",预期:" + (10 * LOOP));
    }
}

输出结果

复制代码
count++ 结果:45623,预期:100000
atomicCount结果:100000,预期:100000

可见性(Visibility)

当一个线程修改了共享变量的值,其他线程能够立即看到这个修改。

保证可见性的方式

  • volatile关键字
  • synchronizedLock
  • final关键字(初始化后可见)
java 复制代码
public class VisibilityDemo {
    // 不使用volatile,可能无法看到修改
    private static boolean flag = true;
    // 使用volatile,保证可见性
    // private static volatile boolean flag = true;
    
    public static void main(String[] args) throws InterruptedException {
        Thread worker = new Thread(() -> {
            System.out.println("工作线程启动,flag=" + flag);
            int count = 0;
            while (flag) {
                count++;
                // 添加打印或sleep可能让线程看到修改(因为上下文切换)
                // 但这不是可靠的方式
            }
            System.out.println("工作线程结束,共执行了" + count + "次循环");
        });
        
        worker.start();
        Thread.sleep(1000);
        System.out.println("主线程修改flag为false");
        flag = false;
        
        worker.join();
        System.out.println("程序结束");
    }
}

运行结果(不使用volatile):

复制代码
工作线程启动,flag=true
主线程修改flag为false
(程序卡住,工作线程永远不会退出)

运行结果(使用volatile):

复制代码
工作线程启动,flag=true
主线程修改flag为false
工作线程结束,共执行了xxxxx次循环
程序结束

有序性(Ordering)

程序执行的顺序按照代码的先后顺序执行。

为什么会乱序

  • 编译器优化:重排指令以提高性能
  • CPU乱序执行:为充分利用CPU流水线
  • 内存系统重排:缓存导致写操作延迟可见
java 复制代码
public class OrderingDemo {
    private static int a = 0, b = 0;
    private static int x = 0, y = 0;
    
    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 100000; i++) {
            a = 0; b = 0; x = 0; y = 0;
            
            Thread t1 = new Thread(() -> {
                a = 1;      // 操作1
                x = b;      // 操作2
            });
            
            Thread t2 = new Thread(() -> {
                b = 1;      // 操作3
                y = a;      // 操作4
            });
            
            t1.start();
            t2.start();
            t1.join();
            t2.join();
            
            // 如果完全按顺序执行,不会出现 x=0,y=0 的情况
            if (x == 0 && y == 0) {
                System.out.println("第" + (i+1) + "次出现重排序:x=" + x + ", y=" + y);
                break;
            }
        }
    }
}

输出结果

复制代码
第xxx次出现重排序:x=0, y=0

Happens-Before原则

Happens-Before是JMM的核心,它定义了哪些操作必须对后续操作可见。

八大规则

规则 说明 示例
程序次序规则 一个线程内,写在前面的操作先行发生于后面的操作 代码顺序
管程锁定规则 unlock先行发生于后续的lock synchronized块
volatile规则 volatile写先行发生于后续的读 volatile变量
线程启动规则 start()先行发生于线程内的所有操作 Thread.start()
线程终止规则 线程所有操作先行发生于join()返回 Thread.join()
线程中断规则 interrupt()先行发生于检测到中断 Thread.interrupted()
对象终结规则 对象构造完成先行发生于finalize() finalize()
传递性规则 A先行于B,B先行于C,则A先行于C 链式关系
java 复制代码
public class HappensBeforeDemo {
    private int value = 0;
    private volatile boolean ready = false;
    
    public void writer() {
        value = 42;      // 操作1
        ready = true;    // 操作2(volatile写)
    }
    
    public void reader() {
        if (ready) {     // 操作3(volatile读)
            System.out.println(value);  // 操作4
            // 由于volatile规则,操作1对操作4可见
            // 所以value一定是42,不会是0
        }
    }
    
    public static void main(String[] args) throws InterruptedException {
        HappensBeforeDemo demo = new HappensBeforeDemo();
        
        Thread writer = new Thread(demo::writer);
        Thread reader = new Thread(demo::reader);
        
        writer.start();
        reader.start();
        
        writer.join();
        reader.join();
    }
}

输出结果

复制代码
42

volatile关键字详解

volatile的两层语义

  1. 保证可见性:写操作立即刷新到主内存,读操作从主内存读取
  2. 禁止指令重排序:在volatile前后设置内存屏障
java 复制代码
public class VolatileDemo {
    // 单例模式的双重检查锁(经典volatile使用场景)
    private static volatile VolatileDemo instance;
    
    private VolatileDemo() {}
    
    public static VolatileDemo getInstance() {
        if (instance == null) {           // 第一次检查(非同步)
            synchronized (VolatileDemo.class) {
                if (instance == null) {   // 第二次检查(同步)
                    instance = new VolatileDemo();  // volatile防止指令重排序
                }
            }
        }
        return instance;
    }
}

volatile vs synchronized

特性 volatile synchronized
保证可见性
保证原子性 否(仅单次读写)
保证有序性 是(禁止重排序)
锁机制 无锁 重量级锁
适用场景 状态标记、单例双重检查 复合操作、代码块

代码Demo合集

Demo1:可见性问题演示

java 复制代码
public class VisibilityProblemDemo {
    private static boolean running = true;
    
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() -> {
            System.out.println("线程开始");
            while (running) {
                // 忙等待,JVM可能优化为 while(true)
            }
            System.out.println("线程结束");
        });
        
        t.start();
        Thread.sleep(1000);
        running = false;
        System.out.println("主线程设置running=false");
    }
}

输出结果

复制代码
线程开始
主线程设置running=false
(程序永远不会结束)

解决方案 :使用volatile修饰running变量

Demo2:volatile解决可见性

java 复制代码
public class VolatileSolutionDemo {
    private static volatile boolean running = true;  // 添加volatile
    
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() -> {
            System.out.println("线程开始");
            while (running) {
                // 现在可以正常退出
            }
            System.out.println("线程结束");
        });
        
        t.start();
        Thread.sleep(1000);
        running = false;
        System.out.println("主线程设置running=false");
        t.join();
        System.out.println("程序正常结束");
    }
}

输出结果

复制代码
线程开始
主线程设置running=false
线程结束
程序正常结束

Demo3:synchronized解决原子性问题

java 复制代码
public class SynchronizedAtomicityDemo {
    private static int count = 0;
    private static final Object lock = new Object();
    private static final int THREAD_COUNT = 10;
    private static final int LOOP = 10000;
    
    public static void main(String[] args) throws InterruptedException {
        Thread[] threads = new Thread[THREAD_COUNT];
        
        for (int i = 0; i < THREAD_COUNT; i++) {
            threads[i] = new Thread(() -> {
                for (int j = 0; j < LOOP; j++) {
                    synchronized (lock) {
                        count++;  // 现在这个操作是原子的
                    }
                }
            });
            threads[i].start();
        }
        
        for (Thread t : threads) {
            t.join();
        }
        
        System.out.println("最终结果:" + count + ",预期:" + (THREAD_COUNT * LOOP));
    }
}

输出结果

复制代码
最终结果:100000,预期:100000

最佳实践与常见问题

什么情况下必须使用volatile?

  • 状态标志:线程停止标志、初始化完成标志
  • 双重检查锁的单例模式
  • 独立观察结果:多个线程读取同一个变量,只有一个线程修改

volatile不能保证原子性的示例

java 复制代码
public class VolatileNotAtomicDemo {
    private static volatile int count = 0;
    
    public static void main(String[] args) throws InterruptedException {
        Thread[] threads = new Thread[10];
        for (int i = 0; i < 10; i++) {
            threads[i] = new Thread(() -> {
                for (int j = 0; j < 1000; j++) {
                    count++;  // 非原子操作,volatile不能保证原子性
                }
            });
            threads[i].start();
        }
        
        for (Thread t : threads) {
            t.join();
        }
        
        System.out.println("预期:10000,实际:" + count);
    }
}

输出结果

复制代码
预期:10000,实际:8654(每次结果不同,小于10000)

JVM参数与调试

bash 复制代码
# 打印JVM内存模型相关信息
java -XX:+PrintFlagsFinal | grep -i "model"

核心总结

  • JMM定义了Java程序在多线程环境下的内存访问规范
  • 主内存存储共享变量,工作内存存储线程副本
  • 三大特性:原子性、可见性、有序性
  • Happens-Before是判断数据竞争和线程安全的核心依据
  • volatile保证可见性和有序性,但不保证复合操作的原子性
  • synchronized可以同时保证三大特性
相关推荐
froginwe111 小时前
SQL LIKE 操作符详解
开发语言
182******20831 小时前
2026年java后端还有机会吗?还能找到工作吗?
java·开发语言
kyriewen112 小时前
你等的Babel编译,够喝三杯咖啡了——用Rust重写的SWC,只需眨个眼
开发语言·前端·javascript·后端·性能优化·rust·前端框架
CSCN新手听安2 小时前
【Qt】Qt窗口(八)QFontDialog字体对话框,QInputDialog输入对话框的使用,小结
开发语言·c++·qt
XS0301062 小时前
Java基础 map集合
java·哈希算法·散列表
tumu_C2 小时前
用std::function减缓C++模板代码膨胀和编译压力的一个场景
开发语言·c++
BT-BOX2 小时前
Matlab 2025B下载安装教程
开发语言·matlab
凤山老林2 小时前
从0到1搭建企业级权限管理系统:Spring Boot + JWT + RBAC实战指南
java·spring boot·后端·权限管理·rbac
逍遥德3 小时前
AI时代,计算机专业大学生学习指南
java·javascript·人工智能·学习·ai编程