Java内存模型(JMM)是Java虚拟机规范中定义的一种抽象内存模型,它规定了多线程环境下,线程如何与主内存及工作内存交互,以及线程间共享变量的可见性、有序性和原子性规则。其核心目标是解决多线程并发中的内存可见性、指令重排序等问题,为开发者提供一套可预测的并发编程语义。


一、JMM的核心组件

  1. 主内存(Main Memory)

    • 存储所有共享变量(实例字段、静态字段等)。
    • 所有线程均可访问,但线程不能直接操作主内存中的变量。
  2. 工作内存(Working Memory)

    • 每个线程独享的私有内存区域。
    • 存储该线程使用的共享变量的副本
    • 线程对变量的所有操作(读/写)必须在工作内存中进行,不能直接读写主内存。

二、JMM的关键规则

  1. 内存交互操作
    JMM定义了8种原子操作(已简化,实际由JVM实现):

    • lock:锁定主内存变量,标识为线程独占。
    • unlock:解锁主内存变量。
    • read:从主内存读取变量到工作内存。
    • load:将read得到的值放入工作内存副本。
    • use:将工作内存变量值传递给执行引擎(如CPU)。
    • assign:将执行引擎结果赋给工作内存变量。
    • store:将工作内存变量值传送到主内存。
    • write:将store传递的值写入主内存变量。

    操作约束

    • readloadstorewrite必须按顺序成对出现
    • 变量必须从主内存read后,才能被use;必须被assign后,才能store回主内存。
  2. 可见性规则

    • 线程修改共享变量后,必须同步回主内存
    • 其他线程需从主内存重新读取该变量才能看到最新值。
    • 关键字保障(如volatile):强制每次访问变量都从主内存读写。
  3. 有序性规则(禁止指令重排序)

    • 编译器和处理器会对指令进行重排序优化。
    • JMM通过happens-before原则(见下文)定义操作间的顺序约束。

三、happens-before 原则

JMM通过happens-before(先行发生)规则定义操作间的可见性与顺序性,无需开发者手动同步。部分规则:

  1. 程序顺序规则:同一线程内的操作,按代码顺序执行。
  2. volatile规则volatile变量的写操作happens-before后续的读操作。
  3. 锁规则(监视器锁):解锁操作happens-before后续的加锁操作。
  4. 传递性:若A happens-before B,B happens-before C,则A happens-before C。
  5. 线程启动规则Thread.start()前的操作happens-before新线程的任何操作。
  6. 线程终止规则:线程中的所有操作happens-before其他线程检测到该线程终止。

示例

1
2
3
4
5
6
7
8
9
10
11
int x = 0;
volatile boolean v = false;

// 线程A
x = 42; // (1)
v = true; // (2) volatile写

// 线程B
if (v) { // (3) volatile读
System.out.println(x); // (4) 保证看到x=42
}

根据happens-before原则:

  • (1) happens-before (2) (程序顺序规则)
  • (2) happens-before (3) (volatile规则)
  • (3) happens-before (4) (程序顺序规则)
    因此 (1) happens-before (4),线程B一定能看到x=42

四、JMM的设计动机

  1. 屏蔽底层差异

    • 不同CPU架构(x86、ARM)内存模型不一致(如内存屏障指令)。
    • JMM提供统一抽象,使Java程序跨平台行为一致。
  2. 平衡性能与正确性

    • 工作内存机制:线程通过本地副本操作数据,减少直接访问主内存的次数,极大提升性能(类比CPU缓存)。
    • 按需同步:开发者通过volatilesynchronized等关键字按需控制同步,避免无谓的性能损耗。
  3. 解决可见性与重排序问题

    • 可见性问题:线程A修改变量后未同步到主内存,线程B读取到旧值。
    • 重排序问题:编译器/处理器重排序导致代码执行顺序与预期不符(如单例模式的DCL问题)。
    • JMM通过内存屏障(Memory Barrier)在底层禁止某些重排序,保障有序性。
  4. 提供明确的并发语义

    • 通过happens-before原则,让开发者无需理解底层细节即可编写正确的并发代码。

五、关键字的语义

关键字 原子性 可见性 有序性
synchronized 退出锁时同步到主内存 临界区内禁止重排序
volatile 每次访问从主内存读写 禁止该变量相关指令重排序
final - 构造函数内正确初始化 禁止final字段初始化重排序

final的特殊规则
若对象引用为final,则其他线程能看到该对象时,其final字段一定已初始化完成。


六、为什么这样设计?

  1. 性能优化
    工作内存机制减少主内存访问(类似CPU缓存),大幅提升执行效率。

  2. 硬件与编译器友好
    允许编译器和处理器在遵守happens-before的前提下自由优化(如指令重排序、寄存器分配)。

  3. 开发者友好性
    通过抽象层(而非暴露硬件细节)让开发者更易编写正确的并发程序。

  4. 可移植性
    统一的内存模型使Java程序在不同硬件和操作系统上行为一致。


总结

Java内存模型通过主内存与工作内存的分离happens-before规则内存屏障技术,在保障多线程可见性、有序性与原子性的同时,兼顾了执行效率与跨平台一致性。其本质是在硬件差异与程序正确性之间建立了一层抽象契约,让开发者能更专注于业务逻辑,而非底层并发细节。理解JMM是编写高效、可靠并发程序的基础。