Java中的Java内存模型(JMM)与happens-before原则详解
字数 2032 2025-11-10 13:21:39

Java中的Java内存模型(JMM)与happens-before原则详解

一、知识描述
Java内存模型(Java Memory Model, JMM)是Java虚拟机规范中定义的一种抽象模型,用于屏蔽各种硬件和操作系统的内存访问差异,确保Java程序在各种平台上都能得到一致的内存访问效果。JMM的核心问题是解决在多线程并发环境下,如何处理共享变量的可见性、原子性和有序性问题。happens-before原则是JMM中定义的一组规则,用于描述两个操作之间的内存可见性关系。

二、知识背景与必要性

  1. 硬件差异:现代计算机系统为了提升性能,普遍采用多级缓存、CPU乱序执行等技术,这导致了不同CPU对同一内存位置的读取可能得到不同结果
  2. 编译器优化:Java编译器(javac)和运行时编译器(JIT)会对指令进行重排序优化,可能改变代码的执行顺序
  3. 线程安全问题:在没有正确同步的情况下,多线程访问共享变量可能出现不可预期的结果

三、JMM的核心概念

  1. 主内存(Main Memory)

    • 所有共享变量都存储在主内存中
    • 主内存是所有线程共享的内存区域
  2. 工作内存(Working Memory)

    • 每个线程有自己的工作内存,存储该线程使用到的变量的主内存副本
    • 线程对变量的所有操作(读取、赋值)都在工作内存中进行
  3. 内存间交互操作

    • lock(锁定):作用于主内存变量,标识为线程独占状态
    • unlock(解锁):作用于主内存变量,释放锁定状态
    • read(读取):从主内存传输变量值到线程工作内存
    • load(载入):把read得到的值放入工作内存的变量副本中
    • use(使用):把工作内存中的变量值传递给执行引擎
    • assign(赋值):把从执行引擎接收的值赋给工作内存中的变量
    • store(存储):把工作内存中的变量值传送到主内存
    • write(写入):把store得到的值放入主内存的变量中

四、happens-before原则详解
happens-before关系定义了两个操作之间的偏序关系,如果操作A happens-before 操作B,那么A操作对内存的修改对B操作可见。

  1. 程序顺序规则(Program Order Rule)

    • 在单个线程中,按照程序代码的顺序,前面的操作happens-before后面的操作
    • 注意:这仅保证在单线程内的执行结果,在多线程环境下仍需考虑重排序
  2. 监视器锁规则(Monitor Lock Rule)

    • 对一个锁的解锁操作happens-before随后对这个锁的加锁操作
    synchronized (lock) {
        // 线程A在此区域内的所有写操作
        sharedVariable = 1;  // 操作A
    } // 解锁操作happens-before线程B的加锁操作
    
    synchronized (lock) {
        // 线程B能看到线程A的写操作结果
        System.out.println(sharedVariable);  // 保证输出1
    }
    
  3. volatile变量规则(Volatile Variable Rule)

    • 对一个volatile变量的写操作happens-before后续对这个变量的读操作
    • volatile变量能防止指令重排序,保证可见性
  4. 线程启动规则(Thread Start Rule)

    • 线程的start()方法调用happens-before该线程的任何操作
    int x = 10;
    Thread t = new Thread(() -> {
        // 此处能看到x=10,因为start()调用happens-beforerun()方法执行
        System.out.println(x);  // 保证输出10
    });
    x = 20;  // 这个修改对线程t不可见
    t.start();
    
  5. 线程终止规则(Thread Termination Rule)

    • 线程中的所有操作都happens-before其他线程检测到该线程已经终止
    Thread t = new Thread(() -> {
        sharedVariable = 100;  // 操作A
    });
    t.start();
    t.join();  // 等待线程终止
    // 此处保证能看到sharedVariable=100
    
  6. 中断规则(Interruption Rule)

    • 对线程interrupt()方法的调用happens-before被中断线程检测到中断事件
  7. 对象终结规则(Finalizer Rule)

    • 对象的构造函数执行结束happens-before它的finalize()方法的开始
  8. 传递性(Transitivity)

    • 如果A happens-before B,且B happens-before C,那么A happens-before C

五、实际应用示例

public class HappensBeforeExample {
    private int x = 0;
    private volatile boolean flag = false;
    
    public void writer() {
        x = 42;           // 操作1
        flag = true;      // 操作2:volatile写
    }
    
    public void reader() {
        if (flag) {       // 操作3:volatile读
            System.out.println(x);  // 操作4:保证输出42
        }
    }
}

分析:

  • 根据程序顺序规则:操作1 happens-before 操作2
  • 根据volatile规则:操作2 happens-before 操作3
  • 根据程序顺序规则:操作3 happens-before 操作4
  • 根据传递性:操作1 happens-before 操作4,因此x=42对操作4可见

六、JMM的内存屏障
为了实现happens-before关系,JMM在底层使用了内存屏障指令:

  1. LoadLoad屏障:确保Load1的数据装载在Load2及后续装载指令之前
  2. StoreStore屏障:确保Store1的数据对其他处理器可见在Store2及后续存储指令之前
  3. LoadStore屏障:确保Load1的数据装载在Store2及后续存储指令之前
  4. StoreLoad屏障:确保Store1的数据对其他处理器可见在Load2及后续装载指令之前

七、总结
Java内存模型通过定义happens-before关系,为开发者提供了一套保证多线程程序正确性的规则。理解JMM和happens-before原则对于编写正确的并发程序至关重要,它帮助我们理解在什么情况下一个线程的写操作对另一个线程可见,从而避免出现内存可见性问题。

Java中的Java内存模型(JMM)与happens-before原则详解 一、知识描述 Java内存模型(Java Memory Model, JMM)是Java虚拟机规范中定义的一种抽象模型,用于屏蔽各种硬件和操作系统的内存访问差异,确保Java程序在各种平台上都能得到一致的内存访问效果。JMM的核心问题是解决在多线程并发环境下,如何处理共享变量的可见性、原子性和有序性问题。happens-before原则是JMM中定义的一组规则,用于描述两个操作之间的内存可见性关系。 二、知识背景与必要性 硬件差异 :现代计算机系统为了提升性能,普遍采用多级缓存、CPU乱序执行等技术,这导致了不同CPU对同一内存位置的读取可能得到不同结果 编译器优化 :Java编译器(javac)和运行时编译器(JIT)会对指令进行重排序优化,可能改变代码的执行顺序 线程安全问题 :在没有正确同步的情况下,多线程访问共享变量可能出现不可预期的结果 三、JMM的核心概念 主内存(Main Memory) 所有共享变量都存储在主内存中 主内存是所有线程共享的内存区域 工作内存(Working Memory) 每个线程有自己的工作内存,存储该线程使用到的变量的主内存副本 线程对变量的所有操作(读取、赋值)都在工作内存中进行 内存间交互操作 lock(锁定):作用于主内存变量,标识为线程独占状态 unlock(解锁):作用于主内存变量,释放锁定状态 read(读取):从主内存传输变量值到线程工作内存 load(载入):把read得到的值放入工作内存的变量副本中 use(使用):把工作内存中的变量值传递给执行引擎 assign(赋值):把从执行引擎接收的值赋给工作内存中的变量 store(存储):把工作内存中的变量值传送到主内存 write(写入):把store得到的值放入主内存的变量中 四、happens-before原则详解 happens-before关系定义了两个操作之间的偏序关系,如果操作A happens-before 操作B,那么A操作对内存的修改对B操作可见。 程序顺序规则(Program Order Rule) 在单个线程中,按照程序代码的顺序,前面的操作happens-before后面的操作 注意:这仅保证在单线程内的执行结果,在多线程环境下仍需考虑重排序 监视器锁规则(Monitor Lock Rule) 对一个锁的解锁操作happens-before随后对这个锁的加锁操作 volatile变量规则(Volatile Variable Rule) 对一个volatile变量的写操作happens-before后续对这个变量的读操作 volatile变量能防止指令重排序,保证可见性 线程启动规则(Thread Start Rule) 线程的start()方法调用happens-before该线程的任何操作 线程终止规则(Thread Termination Rule) 线程中的所有操作都happens-before其他线程检测到该线程已经终止 中断规则(Interruption Rule) 对线程interrupt()方法的调用happens-before被中断线程检测到中断事件 对象终结规则(Finalizer Rule) 对象的构造函数执行结束happens-before它的finalize()方法的开始 传递性(Transitivity) 如果A happens-before B,且B happens-before C,那么A happens-before C 五、实际应用示例 分析: 根据程序顺序规则:操作1 happens-before 操作2 根据volatile规则:操作2 happens-before 操作3 根据程序顺序规则:操作3 happens-before 操作4 根据传递性:操作1 happens-before 操作4,因此x=42对操作4可见 六、JMM的内存屏障 为了实现happens-before关系,JMM在底层使用了内存屏障指令: LoadLoad屏障 :确保Load1的数据装载在Load2及后续装载指令之前 StoreStore屏障 :确保Store1的数据对其他处理器可见在Store2及后续存储指令之前 LoadStore屏障 :确保Load1的数据装载在Store2及后续存储指令之前 StoreLoad屏障 :确保Store1的数据对其他处理器可见在Load2及后续装载指令之前 七、总结 Java内存模型通过定义happens-before关系,为开发者提供了一套保证多线程程序正确性的规则。理解JMM和happens-before原则对于编写正确的并发程序至关重要,它帮助我们理解在什么情况下一个线程的写操作对另一个线程可见,从而避免出现内存可见性问题。