始于足下|

MuXinu

园龄:2年7个月粉丝:3关注:1

Java 内存模型(JMM - Java Memory Model)

Java 内存模型(JMM - Java Memory Model)

1. 什么是 Java 内存模型(JMM)?

Java 内存模型(JMM,Java Memory Model)是 Java 虚拟机(JVM)对多线程访问内存的抽象定义,主要用于屏蔽不同 CPU 和操作系统的内存访问差异,保证 Java 代码在不同平台上能获得一致的并发语义。

JMM 主要解决 多线程环境下的可见性、有序性和原子性 问题。


2. JMM 关键概念

JMM 规定 Java 内存由 主内存(Main Memory)工作内存(Working Memory) 组成:

  • 主内存(Heap,Shared Memory):所有线程共享的存储区域(存放对象、静态变量等)。
  • 工作内存(Thread Stack,Local Cache):每个线程私有的存储区域(存放局部变量、方法调用栈等)。
  • 主内存与工作内存之间的数据同步 由 JMM 规范规定,包括变量的读取(load)、写入(store)和刷新(refresh)等规则

3. JMM 三大特性

JMM 主要解决 可见性、原子性、有序性 三大问题:

特性 作用 解决方案
原子性(Atomicity) 保证操作不可被线程调度机制中断 synchronizedvolatileAtomic 变量
可见性(Visibility) 线程对变量的修改能被其他线程看到 volatilesynchronizedfinal
有序性(Ordering) 代码执行顺序符合程序预期 volatilesynchronized、Happens-Before 规则

4. Java 内存模型的 "Happens-Before" 规则

JMM 通过 Happens-Before 关系 规定多个操作的执行顺序,保证线程安全。关键规则包括:

  1. 程序顺序规则:单线程中,代码按书写顺序执行。
  2. 锁定规则(Monitor Lock Rule):对同一锁的 unlock 操作,必须发生在 lock 之后。
  3. volatile 变量规则:对 volatile 变量的写入,Happens-Before 之后的读取,保证可见性。
  4. 线程启动规则(Thread Start Rule)Thread.start() Happens-Before 线程中的所有操作。
  5. 线程终止规则(Thread Join Rule):线程的所有操作 Happens-Before Thread.join() 之后的操作。
  6. 中断规则(Thread Interrupt Rule):对 Thread.interrupt() 的调用 Happens-Before 线程检测到中断状态的代码。
  7. 对象终结规则(Finalizer Rule):对象的构造函数执行完成 Happens-Before 该对象 finalize() 方法的调用。
  8. 传递性(Transitivity):如果 A Happens-Before B,且 B Happens-Before C,则 A Happens-Before C。

5. JMM 解决并发问题的三种关键方式

1️⃣ volatile:保证可见性 & 防止指令重排序

  • volatile 关键字保证变量对所有线程的可见性
  • 不保证原子性,只能用于 单一写入、多线程读取 的场景。
  • volatile 还会 禁止指令重排序,保证一定的有序性。

示例:

class VolatileExample {
    private static volatile boolean flag = false;

    public static void main(String[] args) {
        new Thread(() -> {
            while (!flag) {} // 线程1 等待 flag 变为 true
            System.out.println("Flag changed!");
        }).start();

        try { Thread.sleep(1000); } catch (InterruptedException e) {}

        flag = true; // 线程2 修改 flag,volatile 让线程1 可见
    }
}

💡 volatile 适用于状态标志变量(如 isRunning),但不能保证 i++ 这样的复合操作的原子性。


2️⃣ synchronized:保证原子性 & 可见性 & 有序性

  • synchronized 保证代码块的原子性
  • 进入 synchronized 代码块时,必须从主内存读取最新变量值,保证可见性。
  • 退出 synchronized 代码块时,必须将修改后的变量值刷新回主内存,保证数据一致性。
  • 还能保证代码块的有序执行,避免指令重排序。

示例:

class SyncExample {
    private int count = 0;

    public synchronized void increment() {  // 线程安全
        count++;
    }
}

3️⃣ Lock & Atomic

  • ReentrantLock 代替 synchronized,支持公平锁、可中断锁等高级功能。
  • AtomicIntegerjava.util.concurrent.atomic 包提供的类使用 CAS + volatile 变量,比 synchronized 更高效。

示例(AtomicInteger 保证原子性):

import java.util.concurrent.atomic.AtomicInteger;

class AtomicExample {
    private AtomicInteger count = new AtomicInteger(0);

    public void increment() {
        count.incrementAndGet();  // 原子操作
    }
}

6. 指令重排序(Instruction Reordering)

JVM 为了提高性能,可能会对指令进行优化重排序,导致执行顺序与代码顺序不同。

  • 单线程环境下无影响
  • 多线程环境下可能导致不可预测的并发问题

示例:

class ReorderExample {
    int a = 0, b = 0, x = 0, y = 0;

    public void method1() {
        a = 1; // ①
        x = b; // ②
    }

    public void method2() {
        b = 1; // ③
        y = a; // ④
    }
}

可能的执行顺序:

  1. ①②③④(符合书写顺序)
  2. ③④①②(由于 CPU 重排序)
  3. ①③②④(部分重排序,导致 x = 1, y = 0

🔹 解决方案:

  • volatile 防止重排序(store-load 屏障)。
  • synchronized / Lock 确保代码执行顺序。

🔹 总结

关键点 作用 解决方案
JMM 线程间内存交互规则 规定工作内存 & 主内存交互
Happens-Before 线程执行顺序 保证 JMM 可见性 & 有序性
原子性 线程安全性 synchronizedAtomic
可见性 保证线程读取最新值 volatilesynchronized
有序性 防止指令重排序 volatilesynchronized
指令重排序 性能优化可能导致并发问题 volatile 变量屏障

本文作者:MuXinu

本文链接:https://www.cnblogs.com/MuXinu/p/18711907

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   MuXinu  阅读(9)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起