volatile 关键字笔记

volatile 关键字笔记
Liuxz一、什么是 volatile?
volatile 是 Java 中的一个关键字,用于修饰变量,保证变量的 可见性 和 禁止指令重排序,但不保证原子性。
二、核心特性
可见性
当一个变量被
volatile修饰时,线程对该变量的修改会 立即被其他线程看到。- 原理:线程修改
volatile变量后,会强制将修改后的值刷新到主内存;其他线程读取时,会直接从主内存加载最新值(而非线程本地缓存)。 - 示例:
- 原理:线程修改
1 | class VolatileDemo { |
线程 A 调用 setFlag() 后,线程 B 的 checkFlag() 会立即感知到 flag 的变化并退出循环。
禁止指令重排序
编译器或 CPU 为优化性能,可能会对代码指令重排序(不影响单线程结果,但可能破坏多线程逻辑)。
volatile会禁止这种重排序,保证代码执行顺序与编写顺序一致。- 典型场景:单例模式的双重检查锁(避免未初始化完成的对象被引用):
1 | class Singleton { |
若不加 volatile,instance = new Singleton() 可能被拆分为「分配内存→引用指向内存→初始化对象」,重排序后可能导致其他线程拿到未初始化的对象。
三、不保证原子性
volatile 不能保证复合操作的原子性(如 i++,本质是「读 - 改 - 写」三步)。
- 反例:多线程自增
volatile变量,结果可能小于预期:
1 | class VolatileAtomicDemo { |
解决:需配合 `synchronized` 或 `AtomicInteger` 保证原子性。
四、适用场景
- 状态标记:如示例中的
flag,用于线程间简单的状态传递(启动 / 停止信号)。 - 双重检查锁单例:禁止指令重排序,保证单例对象正确初始化。
- 简单变量的读写:仅涉及单个变量的读和写(无复合操作)。
五、总结
volatile是轻量级同步机制,性能优于synchronized,但功能有限。- 核心作用:保证可见性、禁止指令重排序,不保证原子性。
- 适合简单的线程间状态同步,复杂场景需结合锁或原子类使用。
我们用生活化的例子,把 volatile 讲得明明白白:
一、先搞懂:没有 volatile 会出啥问题?
电脑里的线程读取变量时,为了速度快,会把主内存的变量 “拷贝” 一份到自己的 “本地缓存”(比如 CPU 缓存)。
就像你抄作业时,把课本(主内存)的内容抄到笔记本(本地缓存)上,之后直接看笔记本,不回头看课本了。
这会导致两个问题:
- 看不见别人的修改:如果别人改了课本内容,你没翻课本,永远不知道最新内容;
- 自己的操作顺序乱了:老师布置 “先写数学再写语文”,你为了省时间先写了语文(对应 CPU 指令重排序),单看你自己没问题,但如果和同学协作(多线程)就会出乱子。
二、volatile 到底是干啥的?
volatile 就像给变量加了两个 “强制规则”,解决上面的问题:
1. 规则 1:保证 “可见性”—— 改了必须让所有人知道
被 volatile 修饰的变量,线程修改它时,必须立刻把新值写回课本(主内存);其他线程读这个变量时,必须直接从课本(主内存)读,不能看自己的笔记本(本地缓存)。
举个例子:
- 线程 A 改了
volatile变量flag = true,马上写回主内存; - 线程 B 之前缓存的
flag是false,但因为加了volatile,它会重新去主内存读,立刻知道flag变了。
没有 volatile 的话,线程 A 改完可能先存在自己的缓存里,线程 B 永远看不到,就会一直卡着。
2. 规则 2:禁止 “指令重排序”—— 必须按规矩来
被 volatile 修饰的变量,涉及它的操作不能乱序执行。
比如单例模式里的 instance = new Singleton(),本质是 3 步:① 分配内存 ② 初始化对象 ③ 把变量指向内存。
没有 volatile 时,CPU 可能优化成 ①→③→②,导致其他线程拿到 “还没初始化好的对象”;加了 volatile 后,就必须按 ①→②→③ 执行,避免出错。
三、volatile 的 “短板”:不保证原子性
volatile 只能管 “读” 和 “写单个变量” 的正确性,但管不了 “多步操作”。
比如 count++,看似简单,实际是 3 步:① 读 count 的值 ② 加 1 ③ 写回新值。这 3 步中间可能被其他线程打断,导致结果出错。
举个例子:两个线程同时执行 count++(初始值 0):
- 线程 A 读
count=0,还没来得及加 1,线程 B 也读count=0; - 线程 A 加 1 写回
1,线程 B 加 1 也写回1; - 最终
count=1,而不是预期的2。
解决办法:这种复合操作,得用 synchronized 锁或者 AtomicInteger 这类原子类。
四、什么时候用 volatile?
- 状态标记:比如用
volatile boolean isStop控制线程启动 / 停止,简单的线程间信号传递; - 单例模式双重检查锁:必须加
volatile禁止重排序,避免拿到未初始化的对象; - 简单变量读写:只涉及单个变量的读和写,没有多步复合操作。
一句话总结
volatile 是个 “轻量级同步工具”,专门解决 “线程看不到变量最新值” 和 “操作顺序乱了” 的问题,但管不了多步操作的原子性,复杂场景得搭配锁或原子类使用。




