同步原语不改变内存布局,而是约束线程对主内存与工作内存的访问:volatile强制读写直达主内存并禁止重排序;synchronized加锁清空工作内存、解锁刷回主内存;final通过编译器和处理器协同保障构造完成即可见。

同步原语(如 synchronized、volatile、final)在 Java 内存模型(JMM)中不是“改变内存布局”,而是通过约束线程对主内存和工作内存的访问行为,来控制变量的读写时机与可见范围。它们不分配或移动内存,但会强制刷新、清空、禁止重排,从而影响变量状态在多线程间的传播效果。
volatile:强制读写直达主内存 + 禁止重排序
一个被 volatile 修饰的变量,每次读操作都从主内存重新加载,每次写操作都立即刷回主内存,并让其他线程缓存中该变量的副本失效。这靠底层插入内存屏障实现:
- 写 volatile 变量前插入 StoreStore 屏障,确保之前所有普通写已刷新;
- 写 volatile 变量后插入 StoreLoad 屏障,防止后续读写被重排到写之前;
- 读 volatile 变量前插入 LoadLoad 屏障,保证前面读操作已完成;
- 读 volatile 变量后插入 LoadStore 屏障,防止后续写被提前。
例如:volatile boolean flag = false;,线程 A 执行 flag = true; 后,线程 B 在 while 循环中读取 flag 就一定能看到 true,不会因缓存旧值而死循环。
synchronized:加锁清空 + 解锁刷回
进入同步块时,线程必须清空工作内存中所有共享变量的副本,后续读取强制从主内存加载;退出同步块时,把工作内存中所有修改过的共享变量全部刷新回主内存。这是 JMM 的 happens-before 规则直接保障的:
- 解锁操作 happens-before 后续任意线程对该锁的加锁操作;
- 因此,A 在 synchronized 块内改了变量 x,B 在后续 synchronized 块中读 x,一定能看到 A 的修改;
- 不仅保证可见性,还天然具备原子性和互斥性。
final:构造完成即可见(前提无 this 逸出)
final 字段的特殊性在于:一旦对象构造方法正常结束,该字段的值对其他线程就是立即可见的。这不是靠运行时刷新,而是 JMM 对 final 语义的硬性规定:
- 编译器禁止将 final 字段的写操作与构造方法中的其他操作重排序;
- 处理器保证 final 字段写入完成后,才允许该对象引用被发布(如赋值给静态变量、放入集合等);
- 只要对象没有发生 “this 逸出”(比如构造函数里把 this 发给其他线程),其他线程拿到该引用后,就能看到正确的 final 值。
对比本质:作用机制不同,但目标一致
三者影响内存的方式有明显区别:
-
volatile是轻量级,只管单个变量的可见性与有序性,不提供原子性(i++ 仍需额外同步); -
synchronized是重量级,以锁为边界,批量刷新/清空变量,同时解决可见性、原子性、互斥; -
final是编译期+运行期协同保障,仅适用于初始化阶段,不可变即安全,无运行时开销。
它们共同服务于 JMM 的核心目标:让多线程对共享变量的操作,在逻辑上“像在单线程中顺序执行一样可预期”。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











