Tavio's blog Tavio's blog
首页
  • JVM底层原理
  • 邪恶多线程
  • MyBatis底层原理
  • Spring底层原理
  • MySQL的优化之路
  • ClickHouse的高性能
  • Redis的快速查询
  • RabbitMQ的生产
  • Kafka的高吞吐量
  • ES的入门到入坑
  • MySQL自增ID主键空洞
  • 前端实现长整型排序
  • MySQL无感换表
  • Redis延时双删
  • 高并发秒杀优惠卷
  • AOP无侵入式告警
  • 长短链接跳转
  • 订单超时取消
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

Tavio Zhang

努力学习的小码喽
首页
  • JVM底层原理
  • 邪恶多线程
  • MyBatis底层原理
  • Spring底层原理
  • MySQL的优化之路
  • ClickHouse的高性能
  • Redis的快速查询
  • RabbitMQ的生产
  • Kafka的高吞吐量
  • ES的入门到入坑
  • MySQL自增ID主键空洞
  • 前端实现长整型排序
  • MySQL无感换表
  • Redis延时双删
  • 高并发秒杀优惠卷
  • AOP无侵入式告警
  • 长短链接跳转
  • 订单超时取消
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • Java多线程
  • Java线程锁
  • Java JMM
    • 一、JMM的核心模型:主内存与工作内存
      • 1.1 主内存(Main Memory)
      • 1.2 工作内存(Working Memory)
      • 1.3 线程与内存的交互规则
    • 二、JMM解决的三大核心问题
      • 2.1 可见性问题:线程间“看不到”变量更新
      • 问题定义
      • 问题示例
      • 解决方案
      • 2.2 原子性问题:操作被中断导致数据错误
      • 问题定义
      • 问题示例
      • 解决方案
      • 2.3 有序性问题:指令重排序破坏逻辑
      • 问题定义
      • 问题示例
      • 解决方案
    • 三、内存屏障:JMM的底层实现机制
      • 3.1 内存屏障的分类与作用
      • 3.2 Java中内存屏障的应用
    • 四、总结
  • Java ThreadLocal
  • Java 线程池
  • Java CompletableFuture
  • 《多线程》笔记
Tavio
2022-04-02
目录

Java JMM

在多线程编程中,线程对共享变量的读写操作可能因CPU缓存、编译器优化等导致数据不一致问题。JMM(Java Memory Model,Java内存模型)并非物理内存结构,而是JVM定义的一套内存访问规范,其核心目标是屏蔽不同硬件和操作系统的内存差异,保证Java程序在多线程环境下的内存可见性、原子性和有序性,让程序在不同平台上表现出一致的并发行为。

# 一、JMM的核心模型:主内存与工作内存

JMM通过抽象“主内存”和“工作内存”,规范了线程对共享变量的访问方式。

# 1.1 主内存(Main Memory)

  • 定义:所有线程共享的内存区域,是JVM对物理内存的抽象(并非完全等同于物理内存,可能包含JVM堆、方法区等共享数据区域)。
  • 存储内容:所有共享变量(如实例变量、静态变量等,局部变量因线程私有不属此类)。

# 1.2 工作内存(Working Memory)

  • 定义:每个线程独有的内存区域,对应CPU的高速缓存、寄存器等硬件存储结构。
  • 存储内容:线程需要操作的共享变量的副本(线程不能直接读写主内存的共享变量,必须通过副本间接操作)。

# 1.3 线程与内存的交互规则

线程对共享变量的操作需遵循以下步骤(JMM定义的原子操作):

  1. 读取(read):从主内存将共享变量的值读到工作内存。
  2. 加载(load):将read得到的值加载到工作内存的变量副本中。
  3. 使用(use):线程执行时,从工作内存的变量副本中读取值并使用(如计算)。
  4. 赋值(assign):线程修改变量后,将新值赋值给工作内存的变量副本。
  5. 存储(store):将工作内存中修改后的变量值传递到主内存。
  6. 写入(write):将store得到的值写入主内存的共享变量中。

示例流程:

[主内存] → 共享变量a=0
   ↓(线程1执行read→load)         ↓(线程2执行read→load)
[线程1工作内存] → a=0副本        [线程2工作内存] → a=0副本
   ↓(线程1执行use→assign:a=1)  ↓(线程2执行use:读取到a=0)
[线程1工作内存] → a=1副本        [线程2工作内存] → a=0副本(可见性问题)
   ↓(线程1执行store→write)
[主内存] → a=1(更新后的值)
1
2
3
4
5
6
7

注意:不同线程的工作内存相互隔离,变量值的传递必须通过主内存,这是多线程内存问题的根源。

# 二、JMM解决的三大核心问题

多线程并发时,因主内存与工作内存的交互机制,可能出现可见性、原子性、有序性问题,JMM通过关键字和底层机制解决这些问题。

# 2.1 可见性问题:线程间“看不到”变量更新

# 问题定义

当线程A修改了共享变量并同步到主内存前,线程B读取的仍是工作内存中未更新的旧副本,导致数据不一致。

# 问题示例

private static boolean flag = false; // 主内存中的共享变量

public static void main(String[] args) throws InterruptedException {
    // 线程1:1秒后将flag改为true
    new Thread(() -> {
        try { Thread.sleep(1000); } catch (InterruptedException e) {}
        flag = true; // 修改工作内存副本,尚未同步到主内存
        System.out.println("线程1:flag已设为true");
    }).start();

    // 线程2:循环判断flag,若为true则退出
    new Thread(() -> {
        while (!flag) { 
            // 始终读取工作内存中的旧副本(false),无法感知线程1的修改
        }
        System.out.println("线程2:检测到flag为true,退出循环");
    }).start();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

现象:线程2可能永远无法退出循环(因看不到flag的更新)。

# 解决方案

JMM通过强制刷新工作内存与主内存的同步,保证可见性:

  • volatile关键字:
    • 写操作:线程修改volatile变量后,会立即通过写屏障将工作内存的新值同步到主内存。
    • 读操作:线程读取volatile变量前,会通过读屏障从主内存刷新最新值到工作内存。
  • synchronized关键字:
    • 释放锁时:线程会将工作内存的变量值同步回主内存(写屏障作用)。
    • 获取锁时:线程会清空工作内存,从主内存重新加载变量(读屏障作用)。

# 2.2 原子性问题:操作被中断导致数据错误

# 问题定义

一个操作或多个操作要么全部执行且执行过程不被中断,要么都不执行。若操作被多线程打断,会导致数据不一致(如count++实际是“读-改-写”三步操作,可能被其他线程插入执行)。

# 问题示例

private static int count = 0;

public static void main(String[] args) throws InterruptedException {
    // 10000个线程同时执行count++
    for (int i = 0; i < 10000; i++) {
        new Thread(() -> {
            try { Thread.sleep(1); } catch (InterruptedException e) {} // 放大竞争概率
            count++; // 非原子操作:读取count→加1→写入count
        }).start();
    }
    Thread.sleep(1000); // 等待所有线程执行完毕
    System.out.println("最终count值:" + count); // 结果通常<10000
}
1
2
3
4
5
6
7
8
9
10
11
12
13

现象:最终结果小于10000,因多个线程同时读取旧值并修改,覆盖了彼此的结果。

# 解决方案

JMM通过限制并发执行范围或使用原子指令保证原子性:

  • synchronized关键字:通过互斥锁保证临界区(同步代码块)内的代码仅被一个线程执行,避免操作被中断。
  • 原子类(如AtomicInteger):基于CPU的CAS(Compare-And-Swap)指令实现原子操作,无需加锁。例如AtomicInteger.incrementAndGet()通过底层Unsafe类的compareAndSwapInt方法,原子性地完成“读-改-写”。

# 2.3 有序性问题:指令重排序破坏逻辑

# 问题定义

编译器或CPU为优化性能,会调整指令的执行顺序(重排序)。单线程下重排序不影响结果,但多线程下可能破坏代码逻辑。

# 问题示例

private static int a = 0;
private static boolean b = false;

public static void main(String[] args) {
    // 线程1:修改a和b
    new Thread(() -> {
        a = 10;    // 操作1
        b = true;  // 操作2(可能被重排序到操作1前执行)
    }).start();

    // 线程2:依赖b的状态读取a
    new Thread(() -> {
        if (b) {   // 若b=true,预期a=10
            System.out.println("a: " + a); // 可能输出a=0(因操作2先执行)
        }
    }).start();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

现象:因重排序,线程1可能先执行b=true再执行a=10,此时线程2读取到b=true但a=0,违背逻辑预期。

# 解决方案

JMM通过禁止特定重排序保证有序性:

  • volatile关键字:通过内存屏障禁止volatile变量与其他变量的重排序(如volatile变量的写操作不能被重排序到之前的操作前,读操作不能被重排序到之后的操作后)。
  • synchronized关键字:同步代码块内的指令执行顺序与代码逻辑一致(因互斥性,单线程执行无重排序问题)。
  • final关键字:禁止final字段的初始化与构造函数外的操作重排序。例如,final变量在构造函数中初始化后,其他线程看到的一定是初始化后的值(不会看到未初始化的中间状态)。

# 三、内存屏障:JMM的底层实现机制

JMM通过内存屏障(Memory Barrier) 强制约束指令重排序和内存可见性,是解决三大问题的底层保障。

# 3.1 内存屏障的分类与作用

  • LoadLoad屏障:禁止屏障后的读操作重排序到屏障前(确保先读完屏障前的变量,再读屏障后的变量)。
  • StoreStore屏障:禁止屏障后的写操作重排序到屏障前(确保先写完屏障前的变量,再写屏障后的变量)。
  • LoadStore屏障:禁止屏障后的写操作重排序到屏障前的读操作前(确保读完后再写)。
  • StoreLoad屏障:禁止屏障后的读操作重排序到屏障前的写操作前(最严格,确保写完后再读,会刷新缓存)。

# 3.2 Java中内存屏障的应用

  • volatile关键字:
    • 写volatile变量后:插入StoreStore屏障(保证之前的写操作已同步到主内存)和StoreLoad屏障(保证写操作完成后再执行后续读操作)。
    • 读volatile变量前:插入LoadLoad屏障(保证先读主内存最新值)和LoadStore屏障(保证读完后再执行后续写操作)。
  • synchronized关键字:
    • 获取锁时:插入LoadLoad屏障和LoadStore屏障(清空工作内存,从主内存加载最新值)。
    • 释放锁时:插入StoreStore屏障和StoreLoad屏障(将工作内存的修改同步回主内存)。

# 四、总结

JMM作为Java多线程内存交互的核心规范,通过抽象主内存与工作内存模型,解决了多线程并发的三大问题:

  • 可见性:通过volatile、synchronized的内存同步机制保证。
  • 原子性:通过synchronized的互斥锁或原子类的CAS指令保证。
  • 有序性:通过volatile、synchronized、final的重排序约束保证。
编辑 (opens new window)
#Java JMM
上次更新: 2026/01/21, 19:29:14
Java线程锁
Java ThreadLocal

← Java线程锁 Java ThreadLocal→

最近更新
01
订单超时取消
01-21
02
双 Token 登录
01-21
03
长短链接跳转
01-21
更多文章>
Theme by Vdoing
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式