volatile关键字解决可见性缺失和指令重排序问题,不保证原子性;强制每次读写主内存并插入内存屏障,适用于状态标志等单次读写场景。

volatile 关键字不解决“不同步”本身,而是专门应对多线程中变量的可见性缺失和指令重排序干扰这两类典型问题。它不能替代锁,也不保证原子性,但对状态标志、单次读写场景非常高效。
强制每次读都从主内存加载最新值
Java 线程默认在自己的工作内存(如 CPU 缓存)中缓存变量副本。一个线程修改了变量,若没刷新到主内存,其他线程就永远读不到新值。
volatile 修饰后,JVM 会确保:
- 每次读取该变量时,都绕过本地缓存,直接从主内存读取
- 每次写入该变量后,立即写回主内存,并触发缓存一致性协议(如 MESI),使其他 CPU 缓存中对应缓存行失效
这样,只要 flag 是 volatile 的,线程 A 写了 flag = true,线程 B 下一次执行 while (!flag) 就一定看到 true,不会卡死。
禁止编译器与 CPU 对 volatile 变量的指令重排
编译器或 CPU 可能为优化性能,把代码顺序打乱(只要单线程结果不变)。但在多线程下,这可能破坏逻辑依赖。
例如:
context = new Context(); // 步骤1:分配对象 context.init(); // 步骤2:初始化字段 ready = true; // 步骤3:设标志位
若 ready 不是 volatile,步骤3可能被提前到步骤1之前——导致其他线程看到 ready == true,却拿到一个未初始化完成的 context。
在调试 Java 后端 API 故障时使用此技能,尤其适用于以下任务:分析日志、跨 Controller、Service 和 Mapper 层追踪请求、定位异常根因、检查 SQL 执行或参数流转,以及生成简洁的诊断结论与修复方案。
volatile 会在读写前后插入内存屏障(Memory Barrier),禁止相关指令越过屏障重排,从而保障“先初始化、再设标志”的语义顺序。
只适用于简单读写,不保证复合操作原子性
volatile 保证的是单个读或单个写的原子性和可见性,但不覆盖组合操作:
-
counter++是“读-改-写”三步,非原子,多个线程同时执行仍会丢数据 -
flag = !flag同样不是原子操作,可能两次读到相同旧值,写出相同结果 -
if (running && count > 0)中,count > 0若非 volatile,其值不可靠
这类场景应改用 AtomicInteger、synchronized 或 ReentrantLock。
修饰引用类型时,只保证引用本身可见
声明 volatile List 表示:
- ✅ 其他线程能看到
list = new ArrayList()这一赋值动作(即新地址可见) - ❌ 但看不到
list.add("item")后列表内容是否更新——除非该集合本身线程安全(如CopyOnWriteArrayList)
同理,volatile 单例模式中,volatile Singleton instance 防止的是构造过程被重排导致“部分初始化对象逸出”,本质仍是靠禁止重排序,而非单纯可见性。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










