Java内存模型(JMM)是定义多线程下共享变量可见性、有序性、原子性的抽象规范,与JVM堆栈等内存布局无关;volatile保证单次读写可见性和禁止重排序,但不保证复合操作原子性;happens-before规则是判断操作间可见性的唯一依据;final字段在正确发布前提下具有初始化完成的内存语义。

Java内存模型(JMM)不是JVM内存布局
很多人一看到“Java内存模型”就去翻JVM堆、栈、方法区的图,这是典型混淆。JMM(Java Memory Model)是抽象规范,定义的是**多线程下共享变量的可见性、有序性、原子性如何被保障**,和运行时内存区域划分(如Heap、Stack)没有直接对应关系。它不规定对象存在哪块物理内存,而规定“某个线程对volatile字段的写,何时对另一个线程可见”。
volatile能禁止重排序但不保证复合操作原子性
声明一个volatile int counter = 0,能确保每次读都从主内存加载、每次写都立即刷回主内存,并插入内存屏障防止指令重排——但这不等于counter++是原子的。
因为counter++实际是三步:read → add → write,中间可能被其他线程打断。常见错误是以为加了volatile就能替代synchronized或AtomicInteger。
- 适用场景:状态标志位(如
running = true)、单次写入后只读的配置项 - 不适用场景:计数器、累加、条件检查+修改(如
if (flag) doSomething()中flag读和doSomething()执行之间无锁保护) - 替代方案:需要原子更新用
AtomicInteger;需要临界区控制用synchronized或ReentrantLock
happens-before规则决定哪些操作可见
JMM用happens-before关系定义操作间的偏序约束,它是判断“线程A的写是否对线程B的读可见”的唯一依据。不是时间先后,而是逻辑依赖。
立即学习“Java免费学习笔记(深入)”;
常见happens-before关系包括:
- 程序顺序规则:同一线程内,按代码顺序,前面的操作
happens-before后面的操作 - 监视器锁规则:对同一个锁,解锁操作
happens-before后续对该锁的加锁操作 -
volatile变量规则:对一个volatile变量的写happens-before后续对该变量的读 - 线程启动规则:
Thread.start()调用happens-before该线程的任意动作 - 传递性:若 A
happens-beforeB,且 Bhappens-beforeC,则 Ahappens-beforeC
注意:没有happens-before关系的操作,编译器和处理器可以任意重排,JVM不保证可见性——哪怕它们在代码里写得前后紧挨着。
final字段的内存语义常被忽略
final不只是语法限制“不可变”,它在JMM中有特殊保障:构造器内对final字段的写,与后续其他线程看到该对象引用之间,存在隐式的happens-before关系。这意味着只要对象正确发布(如没发生逸出),其他线程看到的final字段一定是构造器里赋的值,不会看到默认值(0、null等)。
但这个保障仅限于final字段本身,不延伸到其引用的对象内部状态。例如final List,list本身不可重新赋值,但list.add("x")仍需额外同步。
容易踩的坑:
- 在构造器中把
this引用逸出(如注册监听器、启动线程),会导致其他线程看到未初始化完的final字段 - 认为
final修饰的集合/数组内容也自动线程安全——其实只是引用不可变
真正安全的发布方式:静态初始化、volatile写、synchronized块内发布、使用java.util.concurrent工具类(如ConcurrentHashMap的putIfAbsent)。










