Java volatile 有什么作用?为什么不能保证 i++ 的线程安全?
下面是一段教学用的模拟面试。
🧑💻 面试官:volatile 能解决什么问题?
🙋♂️ 我:让变量对其他线程可见,还能限制重排。
🧑💻 面试官:给计数器加 volatile,两条线程各加一次,结果就一定加二吗?
🙋♂️ 我:不一定,i++ 不是一个完整的原子操作。
🧑💻 面试官:那 volatile 对这个计数器到底保证了什么?又为什么可以用在某些状态发布上?
把「能看见」和「不能同时修改」分开。读写有可见性保证,不代表中间的计算也被锁住了。
面试速答(60 秒版)
volatile 是 Java 内存模型中的同步机制。对一个 volatile 字段的写,与后续对同一字段的读建立相应的 happens-before 关系,因此可以用于状态标记和满足条件的安全发布。
但它没有提供互斥。i++ 包含读取旧值、计算新值、写回三个动作,即使其中的读取和写入都符合 volatile 规则,两个线程仍可能读到同一个旧值,然后分别写回同一个新值,导致一次更新丢失。
所以,只需要发布一个简单状态时可以考虑 volatile;需要原子计数可以使用合适的原子类;需要多个字段共同满足业务约束,通常应使用锁或其他明确的同步方案。不能把“禁止重排”理解成所有代码都不许调整执行方式。

知识点详解:看见了同一个旧值,仍然可能丢一次更新
可见性保证要落在具体的读和写上
假设工作线程循环读取 running,管理线程把它改成 false。
没有合适的同步关系,不能靠“两个线程共享一个变量名”来推导工作线程一定按预期观察到修改。volatile 为这个字段的读写提供了明确的内存模型语义。
这里优先使用 Java 的 happens-before 规则解释。CPU 缓存可以帮助理解实现背景,但不能把“每次强制去某个物理内存读”当成语言层面的完整定义。
同时,观察到修改仍不等于线程会在指定时间内结束。线程调度和循环里的阻塞操作也会影响停止。
把 i++ 拆开,问题就很清楚了
假设 i 初始为 0,两条线程分别执行一次自增。一种可能的交错如下:
| 时刻 | 线程 A | 线程 B |
|---|---|---|
| 1 | 读取 i,得到 0 | |
| 2 | 读取 i,得到 0 | |
| 3 | 计算 0 + 1 | |
| 4 | 写入 1 | |
| 5 | 计算 0 + 1,再写入 1 |
最后是 1,不是 2。
每次读取和写入可以遵守 volatile 的规则,问题仍然存在,因为“读取到写回之间”没有被当成一个不可插入的整体。
这种执行顺序就足以说明保证不成立,不需要声称每次运行都会丢更新。偶尔得到正确结果,也不能证明代码安全。

它为什么又能帮助发布其他数据?
假设发布线程先准备一份配置,再把 volatile 的 ready 写成 true。读取线程读取 ready,确认这次发布完成以后,再访问相应配置。
在正确的顺序和访问协议下,前面的配置写入可以通过 happens-before 的传递关系,被后面的读取看见。JLS 25定义的是这样的顺序与可见性关系。
不过,如果配置之后又被随意修改,或者状态反复切换却没有明确协议,就不能继续套用“一个 volatile 保证所有配置永远安全”的说法。
更容易维护的方式,往往是构建完整的不可变配置,再通过明确的发布入口切换引用。

多个字段的规则,为什么不能只加 volatile?
假设 available 和 reserved 必须始终满足总量约束。给两个字段分别加 volatile,并不会把两次修改组合成一个业务原子操作。
另一个线程可能在两个写入之间读取,看到一个已经更新、另一个还没更新的组合。
需要共同变化时,可以用同一把锁保护整个操作,或者把相关数据组合成一个不可变状态,再使用适当的原子切换策略。重点是整个约束,而不只是单个字段。
本题属于 Java 内存模型,Python 和 TypeScript 没有与 volatile 完全相同的语言机制,因此不提供冒充对应实现的代码。
面试官继续追问
AtomicInteger 和 volatile 的区别是什么?
AtomicInteger 提供 incrementAndGet 等原子更新操作,把一次读改写作为相应原子操作处理。volatile 字段本身没有把普通 i++ 变成这类操作。
volatile 引用指向的对象,内部字段也都自动安全吗?
不是。引用的发布语义,不等于后续所有内部修改都有同步保护。需要分别检查对象的可变状态与访问协议。
volatile 一定比锁更好吗?
不这样比较。它能表达的约束不同。如果需求本来需要互斥,换成 volatile 不是优化,而是删掉了保证。
面试速记卡
- volatile:特定字段读写的可见性与顺序语义。
- i++:读取、计算、写回,不因 volatile 自动整体原子化。
- 发布:要遵守明确的写入与读取顺序。
- 多字段约束:分别加 volatile 不等于共同原子更新。
- 选型:状态发布、原子更新和互斥访问,分别选择合适机制。
公司面试真题
真题根据求职者公开面经整理,题意经过概括,非逐字原话或公司官方题库;本文为 Sunday 的独立解析。
美团 · Java后端 · 实习
volatile 如何保障有序性?(题意整理)
4.21美团Java实习一二面面经 ↗
面试记录为 2020-04-21、2020-04-24;原帖编辑于 2020-11-14京东 · Java后台 · 校招
volatile 能否实现线程安全?(题意整理)
京东 Java 后台三面凉经 ↗
原帖编辑于 2019-08-23(历史校招面经)