JMM通过lock、unlock、read、load、use、assign、store、write八种强制原子操作及happens-before规则保障可见性、原子性与有序性,脱离规则的读写在多核或JIT优化下易出错。

要让 Java 中 JMM 的工作内存与主内存交互正确,关键不是“怎么做”,而是“必须按规则做”——JMM 用一套强制性的操作序列和约束条件来保障可见性、原子性和有序性。脱离这些规则的读写,哪怕代码看起来没问题,也可能在多核 CPU 或 JIT 优化下出错。
八种内存交互操作必须成对、有序执行
JMM 定义了 lock、unlock、read、load、use、assign、store、write 八种原子操作,它们不是可选步骤,而是线程访问共享变量时 JVM 必须遵循的底层协议:
- read + load 必须一起出现:线程不能只从主内存读取值(read),却不把它载入工作内存(load);否则变量副本为空,后续 use 会出错。
- store + write 必须一起出现:线程修改变量后,不能只把新值存到工作内存(store),却不写回主内存(write);否则其他线程永远看不到更新。
- lock 和 unlock 要配对使用:synchronized 块进入时隐式 lock,退出时隐式 unlock;若中途异常退出未解锁,会导致死锁或数据不一致。
可见性靠同步机制触发刷新与重读
工作内存中的变量副本默认不自动更新,JMM 不保证“实时同步”。要让修改对其他线程可见,必须通过明确的同步动作触发内存交互:
- volatile 变量写操作:assign 后强制执行 store + write,并插入写屏障,禁止后续读写重排序;读操作前强制执行 read + load,并插入读屏障,确保读到最新值。
- synchronized 退出时:JVM 保证将当前线程工作内存中所有共享变量(不仅是锁内涉及的)全部 store + write 到主内存。
- final 字段构造完成时:JSR133 保证对象构造过程中对 final 字段的赋值(assign),会在对象引用被其他线程看到前,完成对其它字段的写入并刷新到主内存(happens-before 关系)。
原子性与有序性依赖 happens-before 约束
单个 read/load/use/assign/store/write 操作本身是原子的(long/double 除外),但复合操作(如 i++)天然非原子。JMM 不靠“加锁”来保原子性,而是靠 happens-before 规则建立操作顺序关系,从而间接保障结果一致性:
你是 TypeScript 项目架构专家,专注于脚手架搭建生产就绪的 Node.js 和前端应用。生成包含现代工具链(pnpm、Vite、Next.js)、类型
- 程序顺序规则:同一线程内,前面的操作 happens-before 后面的操作。
- 监视器锁规则:unlock happens-before 后续对该锁的 lock。
- volatile 规则:对 volatile 变量的写 happens-before 后续对该变量的读。
- 传递性:若 A happens-before B,B happens-before C,则 A happens-before C。
只要两个操作满足 happens-before 关系,前者的执行结果就对后者可见,且不会被重排序破坏逻辑顺序。
常见错误:忽略工作内存的“缓存”本质
很多并发 bug 源于误以为“变量在主内存里,改了就大家都能看到”。实际上:
- 局部变量、方法参数不在 JMM 管理范围内,不涉及主存/工作内存交互。
- 未加同步的普通字段,线程可能一直读自己工作内存里的旧副本,即使主内存早已被其他线程改过。
- 看似无害的“先检查再操作”(如 if (flag) doSomething())在没有 volatile 或 synchronized 保护时,flag 的读取和 doSomething() 的执行之间可能被重排序,或 flag 值长期不更新。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










