核心目标:解决多线程下共享数据的「可见性」「有序性」「原子性」问题。
关键原则:JMM 是 Java 规定的「规则手册」,不是实际内存结构。它确保线程间能正确看到数据变化,但不保证代码一定按顺序执行。
1. 原子性(Atomicity)
问题:
count++不是原子操作(底层有读-改-写3 步指令)。
解决方案:synchronized或AtomicInteger。
通俗解释:
想象synchronized(obj)就像锁住一个 房间(obj是门锁)。
- 线程 A 进门后反锁门 → 不能被其他线程打扰(保证
count++完整执行)。- 线程 B 想进门 → 发现门锁着 → 只能等。
关键:必须锁 同一个对象!Object lock = new Object(); // 必须用同一个 lock new Thread(() -> { synchronized(lock) { count++; } // 原子操作 }).start();为什么不用
synchronized频繁?
它是“重量级锁”(类似用铁链锁门),性能低。适合关键代码段。
2. 可见性(Visibility)
问题:线程 A 修改了
run = false,线程 B 还以为run = true(因 CPU 缓存优化)。
死循环示例:boolean run = true; new Thread(() -> { while (run) { /* 死循环 */ } }).start(); run = false; // 主线程改了值,但线程 B 看不到为什么?
JVM 把run存在 本地缓存(CPU 仓库),线程 B 直接从仓库拿值,不看主内存。
解决方案:volatilevolatile boolean run = true; // 每次读都去主内存拿!通俗解释:
volatile像 直接去仓库查货(不走本地缓存),保证修改对所有线程“可见”。
注意:volatile不保证原子性!(如count++仍需synchronized)
3. 有序性(Ordering)
问题:JVM 优化时可能乱序执行指令(“指令重排”),导致多线程出错。
经典案例:Double-Checked Locking 单例public class Singleton { private static volatile Singleton instance; // 必须加 volatile public static Singleton getInstance() { if (instance == null) { synchronized (Singleton.class) { if (instance == null) { instance = new Singleton(); // 这里可能重排! } } } return instance; } }为什么重排会出错?
instance = new Singleton()底层分三步:
- 分配内存
- 初始化对象
- 赋值给
instance
重排风险:若 JVM 先执行步骤 3(赋值),再执行步骤 2(初始化),其他线程可能拿到 未初始化 的对象!
解决方案:volatile禁止重排。
通俗解释:
volatile像 给指令加“顺序标”,保证“先初始化,再赋值”。
4. happens-before(规则手册)
核心:定义“写操作何时对读操作可见”。
代码示例 + 解释(每种情况 1 行代码):
| 情况 | 代码示例 | 简单解释 |
|---|---|---|
| 1. 解锁后可见 | java<br>Object lock = new Object();<br>int x = 0;<br>synchronized(lock) { x = 42; } // 线程A写<br>synchronized(lock) { System.out.println(x); } // 线程B读(保证看到42) | 线程A 解锁 lock 后,x=42 对线程B 加锁后 的读操作可见。 |
| 2. volatile 写后可见 | java<br>volatile boolean flag = false;<br>flag = true; // 线程A写<br>while (!flag) {} // 线程B读(保证看到true) | volatile 保证写操作 立即对其他线程可见。 |
| 3. start 前写可见 | java<br>int data = 42;<br>Thread t = new Thread(() -> System.out.println(data));<br>t.start(); // start前data=42 | t.start() 前 的写(data=42),对线程 t 的读 可见。 |
| 4. 结束前写可见 | java<br>int result = 0;<br>Thread t = new Thread(() -> result = 42);<br>t.start();<br>t.join(); // 等待t结束<br>System.out.println(result); // 保证看到42 | 线程 t 结束前 的写(result=42),对 join() 后的读 可见。 |
| 5. 打断前写可见 | java<br>int status = 0;<br>Thread t = new Thread(() -> { while (true) if (status == 1) break; });<br>t.start();<br>status = 1; // 打断前写<br>t.interrupt(); // 打断t | status=1 在 t.interrupt() 前写,对 t 的 break 可见。 |
| 6. 默认值写可见 | java<br>int a = 0; // 默认值写<br>int b = a; // b=0(保证a的值可见) | 变量初始化(如 a=0)对后续变量(如 b=a) 可见。 |
| 传递性 | java<br>// A happens-before B, B happens-before C → A happens-before C<br>volatile int x = 0; // A<br>x = 1; // B<br>System.out.println(x); // C(保证看到1) | 规则可叠加(如 A→B 且 B→C,则 A→C)。 |
为什么 JMM 重要?
- 不用它:多线程代码像“多人抢手机”——数据乱、死循环、空指针。
- 用它:
synchronized/volatile是 JMM 的“工具”,按规则用就安全。
终极口诀:volatile保可见,synchronized保原子,
happens-before是规则,乱序重排莫乱来!