JMM通过抽象规则、关键字语义和内存屏障,在硬件缓存不一致前提下保障并发代码行为可预期;它定义主内存与工作内存模型,规定volatile写读插入屏障确保可见性与有序性,synchronized实现全量刷新与完整屏障,happens-before提供最小安全契约。

JMM 不是直接“解决”缓存不一致,而是通过抽象规则 + 关键字语义 + 内存屏障,让程序员能在硬件不可靠的前提下写出行为可预期的并发代码。它不消除 MESI、Store Buffer 或重排序,而是定义在哪些点必须强制同步,从而绕过底层弱一致性带来的不确定性。
主内存与工作内存的抽象隔离
每个线程操作的不是物理内存,也不是某个核的 L1 缓存,而是 JMM 定义的“工作内存”——这是对 CPU 缓存 + 寄存器的逻辑封装。所有共享变量初始都存在“主内存”(对应 RAM 抽象),线程读写前必须先拷贝副本到自己的工作内存。这个模型把缓存不一致问题显式暴露出来:你写的值没刷回主内存,别人就看不到。
关键约束:
- 线程不能直接读写主内存,必须经由工作内存中转
- 线程间无法访问彼此的工作内存,通信只能通过主内存完成
- 只有特定动作(如 volatile 写、synchronized 释放锁、线程 start/join)才触发工作内存与主内存的同步
volatile:用内存屏障驯服 Store Buffer
普通变量写入可能卡在 CPU 的 Store Buffer 中,迟迟不发往其他核。volatile 写操作会插入 StoreLoad 屏障(x86 下表现为 lock addl 指令),强制清空当前核的 Store Buffer,并等待所有失效请求被处理完毕。volatile 读则插入 LoadLoad 和 LoadStore 屏障,防止后续读写被重排到它前面。
立即学习“Java免费学习笔记(深入)”;
这使得 volatile 变量具备两个效果:
- 写后立即对其他线程可见(绕过 Store Buffer 延迟)
- 禁止编译器和 CPU 对 volatile 读写做重排序(保障有序性)
注意:它只管单个变量的读写,不保证复合操作原子性(比如 i++ 仍是三步)。
synchronized:全量刷新 + 完整屏障序列
退出 synchronized 块时,JVM 要求将本线程工作内存中所有修改过的变量(包括非 volatile 字段)全部刷新回主内存。背后是一套完整内存屏障序列:清空 Store Buffer、等待 Invalidation Queue 处理完成、广播 Invalidate 消息。
这意味着:
- 临界区内的所有写操作,在锁释放后对其他线程可见
- 进入锁时,会从主内存重新加载所有共享变量(避免使用旧缓存副本)
- 整个块内指令不会被重排序出临界区边界
相比 volatile,它开销更大,但提供原子性 + 可见性 + 有序性三位一体保障。
happens-before 规则:定义可见性的最小安全契约
它不规定硬件怎么做,只声明“如果 A happens-before B,那么 A 的结果对 B 可见”。常见规则包括:
- 程序顺序规则:同一线程中,按代码顺序,前一个操作 happens-before 后一个
- 监视器锁规则:unlock happens-before 后续对该锁的 lock
- volatile 规则:对 volatile 变量的写 happens-before 后续对该变量的读
- 线程启动/终止规则:Thread.start() happens-before 子线程任意动作;子线程所有动作 happens-before Thread.join() 返回
只要满足任一规则,JVM 就必须确保前操作的结果能被后操作观察到——无论底层是 x86 强模型还是 ARM 弱模型,JVM 都会插入必要屏障来兑现这个承诺。


















