volatile通过强制读写主内存和插入内存屏障解决可见性与有序性问题:写操作立即刷入主内存,读操作直接从主内存获取,并禁止相关指令重排;但它不保证原子性,不能替代锁或用于复合操作。
volatile 怎么解决“一个线程改了,另一个线程看不到”的问题
根本原因是 Java 内存模型(JMM)里每个线程有自己工作内存,变量读写默认走本地缓存,不自动同步到主内存。没加
时,
被主线程设为
,但子线程可能一直从自己缓存里读
,死循环。
加上
后:
- 写操作:线程一修改
,JVM 强制把该值立刻刷入主内存
- 读操作:线程二每次读
,都绕过本地缓存,直接从主内存取最新值
- 底层靠内存屏障(
前缀指令)保证这点,不是靠轮询或延迟
常见误用:
- 把
当成锁用,比如对
加 volatile —— 它不保证原子性,仍会丢数据
- 在只被单线程访问的变量上加 volatile,纯属冗余,无意义
为什么 volatile 能阻止“代码明明写在前面,却跑到后面执行”
编译器和 CPU 为了性能,会对指令重排。例如这段初始化代码:
看似两步,但实际可能被重排为:
1. 分配
对象内存
2. 把
设为
3. 初始化
字段(此时字段还是默认值)
另一个线程看到
就去用
,结果拿到未初始化完的对象。
在这里加给
就能破局:
- 写
前,插入 store 屏障 → 确保上面所有指令(包括
初始化)必须先完成
- 读
后,插入 load 屏障 → 确保后续读
不会提前到
判断之前
- 注意:这个效果只对 volatile 变量本身及其前后操作生效,不是全局禁止重排
volatile 和 synchronized 的关键区别在哪
两者都涉及内存语义,但作用范围和代价完全不同:
进入时强制从主内存读所有共享变量,退出时强制把所有变更刷回主内存 —— 是“全量同步”
只对声明它的那个变量起效,读写都只针对它自己 —— 是“单点同步”
有锁开销(阻塞、上下文切换),
是无锁的,但仅限于简单状态标志场景
天然保证原子性和可见性;
只保可见性和有序性,不保原子性
典型错误:
- 用
替代
——
是三步操作,volatile 挡不住中间被打断
- 在
块里再对某个变量加
—— 多余,synchronized 已经覆盖了内存语义
哪些场景适合用 volatile,哪些坚决不能用
适合:
- 状态标志位:如
、
- 一次性安全发布(Safe Publication):如双重检查锁里的 instance 字段必须 volatile,否则可能返回构造未完成的对象
- 与 Lock 或 CAS 配合使用时的状态协调变量(如 AQS 中的部分字段)
不适合:
- 任何需要复合操作原子性的场合,比如 “读-改-写”(
)、“检查后执行”(
)
- 被多个线程频繁读写的计数器、累加器 —— 用
或
- 初始化逻辑复杂、依赖多个变量协同的场景 —— volatile 无法建立变量间的 happens-before 关系
最容易被忽略的一点:volatile 的有序性保障是“相对于它自己”的。如果代码里混用非 volatile 变量,屏障不会自动延伸过去 —— 必须靠明确的读写顺序或额外同步机制兜底。
volatilerunningfalsetruevolatilerunning = falserunninglockvolatilecount++context = new Context();
inited = true;ContextinitedtrueContextinited == truecontextvolatileinitedinited = truecontextinitedcontextinitedsynchronizedvolatilesynchronizedvolatilesynchronizedvolatilevolatile int counterAtomicIntegercounter++synchronizedvolatilevolatile boolean shutdownRequestedvolatile boolean isRunningvalue += 1if (count > 0) count--AtomicIntegerLongAdder