跳转至

JMM

JMM 是“Java Memory Model”,它是对多线程读写共享变量时的规则抽象 。

JMM 规定了多个线程如何通过“主内存 + 各自工作内存”读写共享变量,从而保证多线程的 可见性、有序性、原子性 。

1. 主内存 & 工作内存(线程本地内存)

JMM 抽象出两类内存 :

  • 主内存(Main Memory)
    • 存放所有共享变量(类似“统一的大仓库”)。
  • 工作内存(Working Memory)
    • 每个线程拷贝一份共享变量的副本到自己的工作内存里,只能操作这份副本。
    • 修改完成后再同步回主内存,别的线程再从主内存刷新。

问题就出在:刷新和回写不是实时的,所以会有“看不到别人最新修改”的情况 。

2. 并发三大特性(JMM 要解决啥)

1)可见性(Visibility)

  • 含义:一个线程对共享变量的修改,其他线程能够立刻看到 。
  • 默认情况下,线程可能一直用自己工作内存里的旧值。
  • 保证办法:
    • volatile
    • synchronized
    • Lock 等 。

2)有序性(Ordering)

  • 编译器和 CPU 会对指令做重排序,只要单线程下结果一致就行,多线程下就可能出问题 。
  • volatile 和同步(锁)可以在一定程度上禁止或约束重排序。

3)原子性(Atomicity)

  • 一系列操作要么全部执行、要么都不执行,不会被打断 。
  • i++ 不是原子操作,分为读-改-写,因此多线程下会丢更新。
  • 保证办法:
    • 锁(synchronized / ReentrantLock)
    • 原子类(AtomicInteger 等)。

3. happens-before 规则(判断“是否有顺序保障”的工具)

JMM 用 happens-before 抽象“前一个操作的结果对后一个操作可见且有顺序约束” 。核心规则有:

  • 程序顺序规则:同一线程内,代码顺序上的前面 happens-before 后面。
  • 锁规则:对同一把锁的解锁 happens-before 后续的加锁。
  • volatile 规则:对一个 volatile 变量的写 happens-before 后续对该变量的读。
  • 传递性:A happens-before B,B happens-before C,则 A happens-before C 。

如果两个操作之间不存在任何 happens-before 关系,JVM 可以重排序,它们对彼此不保证可见性和顺序 。

JMM 典型例子:可见性问题

代码示例:没有可见性保证

public class JmmVisibilityDemo {

    private static boolean running = true; // 未加 volatile

    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() -> {
            System.out.println("worker start");
            while (running) {  // 可能一直用工作内存中的旧值
                // do something
            }
            System.out.println("worker end");
        });
        t.start();

        Thread.sleep(1000);
        running = false; // 主线程修改 running
        System.out.println("main set running = false");
    }
}

现象(在某些环境中很容易复现):

  • 主线程把 running 改为 false 了,但子线程可能一直“死循环”,看不到变化。
  • 原因:子线程读的是自己工作内存里的旧副本,从未刷新。

使用 volatile 修复可见性

public class JmmVisibilityDemoFixed {

    private static volatile boolean running = true; // 加 volatile

    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() -> {
            System.out.println("worker start");
            while (running) {
                // do something
            }
            System.out.println("worker end");
        });
        t.start();

        Thread.sleep(1000);
        running = false; // 对 volatile 变量的写,happens-before 后续读
        System.out.println("main set running = false");
    }
}

关键点 :

  • volatile 保证:
    • 写操作会立刻刷新到主内存,并让其他线程的工作内存失效;
    • 读操作每次都从主内存读取最新值。
  • 同时具有一定的有序性保证(不允许对 volatile 前后的指令进行某些重排序)。

JMM(并发语义)

  • 目的:搞清楚“多线程如何安全地读写共享变量”。
  • 记忆路径:
    • 主内存 & 工作内存。
    • 三大特性:原子性、可见性、有序性。
    • 关键工具:volatile、synchronized、Lock、happens-before 规则。

未完待续:

分别帮你画出一张“JVM 运行时数据区”脑图式文字版,再单独做一份“JMM + volatile + synchronized”的专题讲解