Java内存模型(JMM)是规范多线程共享变量读写合法性的抽象模型,核心是定义动作顺序与可见性规则,而非JVM运行时内存布局;其日常落脚点是happens-before关系,如volatile写happens-before后续读、解锁happens-before后续加锁等;final字段在构造正常完成前提下保证初始化值可见,但不保障其他字段;并发安全必须依赖明确的happens-before,而非“看起来同步”的直觉。

Java内存模型不是JVM内存结构
很多人一看到“Java内存模型”就去翻JVM堆、栈、方法区的划分,这是典型混淆。JMM(Java Memory Model)和运行时内存布局完全无关,它是一套**关于多线程读写共享变量时,什么结果是合法的、什么行为是被允许的**抽象规范。它的核心目标是:在不牺牲性能的前提下,给程序员提供可理解的内存可见性保证。
关键点在于:JMM定义的是“动作顺序”和“可见性规则”,不是物理内存地址怎么分配。比如你声明一个volatile int flag = 0,JMM规定后续对flag的写必须对其他线程立即可见——这个“立即”不是指纳秒级同步,而是指禁止重排序 + 强制刷新到主内存 + 强制从主内存读取。
happens-before规则才是日常编码的抓手
你几乎不会直接操作JMM,但一定会依赖它的推论:happens-before关系。只要A happens-before B,那么A的执行结果对B就是可见的,且A不能被重排序到B之后。
常见成立的happens-before关系包括:
立即学习“Java免费学习笔记(深入)”;
-
volatile写 happens-before 后续任意线程对该volatile变量的读 - 锁的解锁 happens-before 后续同一把锁的加锁
- 线程
start()happens-before 该线程中任意动作 - 线程中所有动作 happens-before 其
join()返回 - 传递性:如果A happens-before B,B happens-before C,则A happens-before C
注意:synchronized块内修改普通变量,其可见性靠的是“解锁-加锁”的happens-before链,不是靠synchronized本身修饰了那个变量。这也是为什么不用volatile也能实现部分可见性——但前提是用对了锁的范围。
final字段的内存语义常被低估
final不只是语法限制“不可变”,它在JMM里有特殊保障:只要构造函数正常结束(没this逃逸),其他线程看到该对象时,一定能看到final字段的正确初始化值,哪怕没用volatile或锁。
但这不等于整个对象安全。例如:
class Holder {
final int x;
int y; // 非final
Holder(int x, int y) {
this.x = x;
this.y = y; // 可能被其他线程看到未初始化值
}
}
如果Holder实例发布时发生逸出(如构造中把this传给静态集合),那y的值就不可靠——final只保它自己,不保别的字段。
不要试图靠“看起来会同步”来写并发代码
比如写if (flag) { doSomething(); },以为flag是boolean就天然线程安全;或者认为两次System.out.println之间一定能看到中间状态。这些全是错觉。
JMM允许编译器、JIT、CPU做各种优化,包括:
- 把
flag = true重排序到doWork()之前(若无happens-before约束) - 线程把
flag缓存在寄存器,永远不读主内存 - 即使写了
volatile flag,也不代表doSomething()里的非volatile操作会被“拖着一起同步”
真正可靠的只有明确建立的happens-before关系。复杂逻辑别拼凑多个volatile或synchronized块,优先考虑java.util.concurrent包里的工具类——它们内部已严格遵循JMM契约。










