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”的专题讲解