Sunday面试指南

Java volatile 有什么作用?为什么不能保证 i++ 的线程安全?

下面是一段教学用的模拟面试。

🧑‍💻 面试官:volatile 能解决什么问题?

🙋‍♂️ 我:让变量对其他线程可见,还能限制重排。

🧑‍💻 面试官:给计数器加 volatile,两条线程各加一次,结果就一定加二吗?

🙋‍♂️ 我:不一定,i++ 不是一个完整的原子操作。

🧑‍💻 面试官:那 volatile 对这个计数器到底保证了什么?又为什么可以用在某些状态发布上?

把「能看见」和「不能同时修改」分开。读写有可见性保证,不代表中间的计算也被锁住了。

面试速答(60 秒版)

volatile 是 Java 内存模型中的同步机制。对一个 volatile 字段的写,与后续对同一字段的读建立相应的 happens-before 关系,因此可以用于状态标记和满足条件的安全发布。

但它没有提供互斥。i++ 包含读取旧值、计算新值、写回三个动作,即使其中的读取和写入都符合 volatile 规则,两个线程仍可能读到同一个旧值,然后分别写回同一个新值,导致一次更新丢失。

所以,只需要发布一个简单状态时可以考虑 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 的规则,问题仍然存在,因为“读取到写回之间”没有被当成一个不可插入的整体。

这种执行顺序就足以说明保证不成立,不需要声称每次运行都会丢更新。偶尔得到正确结果,也不能证明代码安全。

两次 i++,为什么只得到 1?

它为什么又能帮助发布其他数据?

假设发布线程先准备一份配置,再把 volatile 的 ready 写成 true。读取线程读取 ready,确认这次发布完成以后,再访问相应配置。

在正确的顺序和访问协议下,前面的配置写入可以通过 happens-before 的传递关系,被后面的读取看见。JLS 25定义的是这样的顺序与可见性关系。

不过,如果配置之后又被随意修改,或者状态反复切换却没有明确协议,就不能继续套用“一个 volatile 保证所有配置永远安全”的说法。

更容易维护的方式,往往是构建完整的不可变配置,再通过明确的发布入口切换引用。

用 ready 发布,不是保护所有后续修改

多个字段的规则,为什么不能只加 volatile?

假设 available 和 reserved 必须始终满足总量约束。给两个字段分别加 volatile,并不会把两次修改组合成一个业务原子操作。

另一个线程可能在两个写入之间读取,看到一个已经更新、另一个还没更新的组合。

需要共同变化时,可以用同一把锁保护整个操作,或者把相关数据组合成一个不可变状态,再使用适当的原子切换策略。重点是整个约束,而不只是单个字段。

本题属于 Java 内存模型,Python 和 TypeScript 没有与 volatile 完全相同的语言机制,因此不提供冒充对应实现的代码。

面试官继续追问

AtomicInteger 和 volatile 的区别是什么?

AtomicInteger 提供 incrementAndGet 等原子更新操作,把一次读改写作为相应原子操作处理。volatile 字段本身没有把普通 i++ 变成这类操作。

volatile 引用指向的对象,内部字段也都自动安全吗?

不是。引用的发布语义,不等于后续所有内部修改都有同步保护。需要分别检查对象的可变状态与访问协议。

volatile 一定比锁更好吗?

不这样比较。它能表达的约束不同。如果需求本来需要互斥,换成 volatile 不是优化,而是删掉了保证。

面试速记卡

  • volatile:特定字段读写的可见性与顺序语义。
  • i++:读取、计算、写回,不因 volatile 自动整体原子化。
  • 发布:要遵守明确的写入与读取顺序。
  • 多字段约束:分别加 volatile 不等于共同原子更新。
  • 选型:状态发布、原子更新和互斥访问,分别选择合适机制。

公司面试真题

真题根据求职者公开面经整理,题意经过概括,非逐字原话或公司官方题库;本文为 Sunday 的独立解析。

浏览公司面试真题 →
简历汪永久免费在线制作简历,模板直接套用、导出无水印,永久免费、下载免费,不需要付费解锁任何功能。去写简历