一、内存模型的前世今生
1.1 内存操作的混沌时代
// 2002年著名问题代码
public class MemoryChaos {
int a = 0;
boolean flag = false;
void writer() {
a = 1; // (1)
flag = true; // (2)
}
void reader() {
if (flag) { // (3)
assert a == 1 : "魔幻时刻来临!"; // 可能断言失败
}
}
}
历史背景:在JSR-133规范发布前,该断言可能抛出令人费解的错误
二、JMM的三维世界观
2.1 上帝视角的内存构架
volatile读/写
volatile读/写
普通读/写
同步机制
主内存
线程工作内存
线程工作内存
线程工作内存
关键特征:每个线程看到的变量副本可能不同
2.2 内存交互八大原子操作
| 操作名称 | 作用域 | 语义特征 |
|---|---|---|
| lock(锁定) | 主内存变量 | 标识变量独占状态 |
| unlock(解锁) | 主内存变量 | 释放变量独占状态 |
| read(读取) | 主内存 -> 线程 | 变量传输的起始动作 |
| load(载入) | 主内存 -> 工作内存 | 建立变量关联 |
| use(使用) | 工作内存变量 | 执行引擎操作变量的起点 |
| assign(赋值) | 工作内存变量 | 变更变量的触发点 |
| store(存储) | 工作内存 -> 主内存 | 准备写回的前置动作 |
| write(写入) | 工作内存 -> 主内存 | 完成变量更新 |
三、JMM的铁血三大纪律
3.1 原子性规则
long value = 0L; // 在32位系统中写long可能不是原子的 // 正确姿势 AtomicLong atomicValue = new AtomicLong(0);
突破法则:
-
synchronized建立操作原子边界
-
CAS操作实现无锁原子性
3.2 可见性机制
public class VisibilityWar {
// 没有volatile修饰的变量可能永远不可见
boolean ready = false;
void prepare() {
// ...复杂计算
ready = true; // 幽灵写入
}
void execute() {
while (!ready) {} // 死循环陷阱
// 执行后续操作
}
}
破局神器:volatile的MESI缓存一致性协议
3.3 有序性控制
int x = 0;
int y = 0;
// 线程1
x = 1; // (1)
y = 2; // (2)
// 线程2
if (y == 2) {
System.out.println(x); // 可能输出0还是1?
}
重排序风险:编译器和处理器可能调整(1)(2)执行顺序
四、JMM的兵器谱
4.1 happens-before关系网
程序顺序规则
锁定规则
volatile规则
线程启动规则
传递性规则
对象终结规则
4.2 内存屏障四重奏
| 屏障类型 | 典型场景 | 作用效果 |
|---|---|---|
| LoadLoad屏障 | volatile读之后,普通读之前 | 禁止上方读与下方读重排序 |
| StoreStore屏障 | volatile写之前,普通写之后 | 禁止上方写与下方写重排序 |
| LoadStore屏障 | 普通读之后,volatile写之前 | 禁止上方读与下方写重排序 |
| StoreLoad屏障 | volatile写之后,volatile读之前 | 全能型屏障 |
五、生死时速:DCL单例的涅槃之路
5.1 致命陷阱
public class Singleton {
private static Singleton instance;
public static Singleton getInstance() {
if (instance == null) { // 第一次检测
synchronized (Singleton.class) {
if (instance == null) { // 第二次检测
instance = new Singleton(); // 问题根源!
}
}
}
return instance;
}
}
风险剖析:
-
对象半初始化(a=0)
-
引用提前曝光
-
指令重排序
5.2 完美形态
public class SafeSingleton {
// volatile关键救赎
private volatile static SafeSingleton instance;
public static SafeSingleton getInstance() {
if (instance == null) {
synchronized (SafeSingleton.class) {
if (instance == null) {
instance = new SafeSingleton();
// 1.分配内存
// 2.初始化对象(禁止重排序到3之后)
// 3.设置引用地址(volatile写)
}
}
}
return instance;
}
}
内存屏障生效点:volatile写操作插入StoreStore屏障
六、JMM与其他内存模型的巅峰对决
| 特性 | JMM | 物理硬件模型 |
|---|---|---|
| 可见性控制 | happens-before保证 | 缓存一致性协议 |
| 原子操作粒度 | long/double特殊处理 | CPU字长决定 |
| 重排序约束 | as-if-serial语义 | 处理器自身内存模型 |
| 同步机制 | synchronized/volatile | 内存屏障指令 |
七、生产级JMM诊断工具箱
7.1 可视化工兵
// Java对象内存布局查看 System.out.println(ClassLayout.parseInstance(obj).toPrintable());
输出示例:
7.2 压力测试仪
JCStress测试框架:检测底层内存可见性问题
@Outcome(id = "0", expect = Expect.ACCEPTABLE_INTERESTING)
@State
public class JMMVisibilityTest {
int x;
volatile int y;
// 测试用例...
}
7.3 线上侦察兵(Arthas)
watch com.example.ConcurrencyDemo * '{params,returnObj,throwExp}' \
-x 3 -b -n 5
thread -b # 检测线程阻塞
八、避坑指南:JMM七大天坑
-
双重检查锁未加volatile -> 单例对象半初始化
-
自认为final安全发布 -> final域必须通过正确构造
-
误用volatile修饰数组 -> volatile只保证引用可见,数组元素无保障
-
依赖非volatile的循环退出标志 -> 使用while(!stop)可能永不退出
-
错估长整型原子性 -> 32位JVM中long/double的写入非原子
-
误用ThreadLocal做缓存 -> 线程池复用导致数据污染
-
乐观锁中的ABA问题 -> AtomicStampedReference是解决方案
九、JMM性能调优启示录
9.1 缓存行优化
// 伪共享问题典型场景
class FalseSharing {
volatile long x; // 与y可能在同一缓存行
volatile long y;
}
// 解决方案-缓存行填充
class PaddedAtomicLong extends AtomicLong {
public volatile long p1, p2, p3, p4, p5, p6 = 7L;
}
9.2 域挨批优化
// @Contended注解(JDK8+)
@sun.misc.Contended
class ContendedData {
volatile long value;
}
十、至臻境界:JMM的哲学思考
10.1 并发编程三重境界
-
看山是山:仅关注代码表面行为
-
看山不是山:理解底层内存交互机制
-
看山还是山:掌握抽象模型的本质规律
10.2 设计模式与JMM
-
Immutable Object模式:规避所有内存可见性问题
-
Guarded Suspension模式:使用wait/notify的正确姿势
-
Producer-Consumer模式:BlockingQueue的happens-before保障
附录:JMM高频面试七连击
-
volatile如何保证可见性和有序性?
-
synchronized与JMM三特性关系?
-
什么是指令重排序?JMM如何处理?
-
final字段的初始化安全如何保证?
-
DCL单例为什么要加volatile?
-
JMM中的as-if-serial语义是什么?
-
如何证明JIT编译器进行了指令优化?
通过透彻理解JMM的运行机制,开发者才能真正做到:
-
准确定位诡异并发问题
-
编写可靠的高性能并发代码
-
深度调优系统并发性能
-
在面试中展现实战真功夫
深入剖析Java内存模型(JMM)
372

被折叠的 条评论
为什么被折叠?



