volatile通过StoreStore+StoreLoad写屏障和LoadLoad+LoadStore读屏障保证有序性与可见性,但不保证原子性。

有序性不是代码写在前面就一定先执行,而是指程序逻辑要求的执行顺序,在多线程环境下必须被真实保证。CPU 和 JVM 为了性能会做指令重排序——比如把读操作提前、把写操作延后,单线程下不影响结果,但多线程中可能让其他线程看到“乱序”的状态。
内存屏障就是用来约束这种重排序的底层机制,它像一道“关卡”,强制某些操作必须按指定顺序跨过它。
有序性问题的真实表现
比如两个线程同时操作共享变量:
- 线程 A 执行
flag = true; x = 42; - 线程 B 观察到
flag == true,却读到x == 0
这并非 bug,而是因为没有屏障时,JVM 或 CPU 可能把 x = 42 提前到 flag = true 之前,或把读 x 提前到读 flag 之后。B 看到 flag 已更新,但 x 还没写完——这就是有序性被破坏。
四种内存屏障如何守住顺序
每种屏障锁定一类操作组合:
- LoadLoad:前一个读必须完成,后一个读才能开始
- StoreStore:前一个写必须刷入主内存,后一个写才能开始
- LoadStore:读操作必须完成,后面的写才允许执行
- StoreLoad:最重的屏障,前一个写必须对所有 CPU 可见,后一个读才能开始
它们不改变代码行序,而是告诉硬件“这里不能乱动”。
Java 中的屏障落地方式
你不用手写 mfence 或 dmb 指令,JVM 自动在关键语义处插入对应屏障:
- volatile 写:自动加 StoreStore + StoreLoad
→ 保证flag = true的修改立即可见,且其前的写(如x = 42)不会被重排到它后面 - volatile 读:自动加 LoadLoad + LoadStore
→ 保证读flag时,后续依赖它的操作(如读x)不会被提前 - synchronized 进入:加 LoadLoad + LoadStore
- synchronized 退出:加 StoreStore + StoreLoad
这些屏障共同支撑了 happens-before 规则——比如“volatile 写 happens-before 后续 volatile 读”,背后就是 StoreLoad 和 LoadLoad 屏障在起作用。
屏障不是万能,但不可或缺
它不解决原子性(比如 i++ 仍需锁或 CAS),也不直接保证数据一致性(还需配合正确设计)。但它为可见性和有序性提供了硬件级支撑,是 volatile、synchronized、Lock、甚至 Concurrent 包底层正确运行的基础。没有它,再多的上层封装都可能在多核 CPU 上失效。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











