60 秒回答模板

volatile 的作用是给变量建立特殊内存语义:写 volatile 变量对后续读它的线程可见,并通过内存屏障限制相关指令重排序。它适合状态开关、安全发布、DCL 单例引用这类单变量可见性场景,但不能让 i++、检查再执行、多字段一致性这些复合操作变成原子操作。锁不仅保证可见性,还提供互斥和临界区边界,所以 volatile 不能普遍替代锁;需要原子计数用 AtomicInteger 或 LongAdder,需要维护不变式用 synchronized、ReentrantLock 或更高层并发工具。

考点 核心机制与工程取舍
难度 中高频面试题
回答目标 按定义、机制、场景讲清楚

深入解析

01

可见性语义

普通变量可能被线程长期读到旧值,volatile 写入会让后续读同一变量的线程看到最新写入,并建立 happens-before 关系。它解决的是“别人改了我能不能看见”的问题。

02

有序性语义

volatile 会插入或等价产生内存屏障,限制编译器和 CPU 对 volatile 读写附近指令的重排序。典型用途是双重检查锁中防止对象引用赋值先于构造初始化被其他线程看见。

03

不保证复合原子性

i++ 包含读取、加一、写回三步,多个线程可能同时读到同一个旧值再分别写回。volatile 只能让每次读写可见,不能把这三步合成不可打断的原子操作。

04

不能维护多变量不变式

如果业务要求 a 和 b 同时满足某个关系,或者先检查库存再扣减,volatile 无法保护检查和更新之间不被其他线程插入。此时需要锁、CAS 循环、数据库约束或专门的并发结构。

05

按场景选工具

停止标记、配置版本号、单次发布可以用 volatile;高并发计数用 AtomicLong、LongAdder;保护临界区、组合状态和条件等待用锁或并发工具。回答时要把边界说清,而不是绝对化。

易错点

  • 说 volatile 能替代锁,忽略互斥和复合操作原子性。
  • 只提可见性,不提有序性和 happens-before。
  • 用 volatile 保护库存扣减、余额转账等多步业务不变式。
  • 把 volatile 和 CAS、Atomic 类混为一谈。

面试官追问

volatile 能保证 i++ 线程安全吗?

不能。i++ 是读、改、写复合操作,volatile 只能保证每次读写的可见性和顺序约束,不能阻止两个线程同时基于同一个旧值计算。

DCL 单例为什么要用 volatile?

对象创建可能被重排序为分配内存、引用赋值、执行构造。没有 volatile,另一个线程可能看到非 null 引用,但对象还没初始化完成。volatile 限制这种发布重排序。

volatile 和 synchronized 的可见性有什么关系?

两者都能提供可见性,但 synchronized 还提供互斥和临界区边界。进入和退出锁也有内存语义,能保证锁内修改对后续获得同一把锁的线程可见。

什么时候 volatile 足够?

当状态更新是单变量赋值,且不需要把读取和后续操作绑定成原子整体时通常足够,例如停止标记、配置开关、一次性发布引用。只要有复合条件或多字段一致性,就要更强同步。