真实面经题目 · 原创解析
volatile 的原理是什么,能否替代锁?
考察 Java 内存模型,核心是 volatile 保证可见性和有序性,但不保证复合操作原子性,不能替代锁保护临界区。
真实面经题目 · 原创解析
考察 Java 内存模型,核心是 volatile 保证可见性和有序性,但不保证复合操作原子性,不能替代锁保护临界区。
volatile 的作用是给变量建立特殊内存语义:写 volatile 变量对后续读它的线程可见,并通过内存屏障限制相关指令重排序。它适合状态开关、安全发布、DCL 单例引用这类单变量可见性场景,但不能让 i++、检查再执行、多字段一致性这些复合操作变成原子操作。锁不仅保证可见性,还提供互斥和临界区边界,所以 volatile 不能普遍替代锁;需要原子计数用 AtomicInteger 或 LongAdder,需要维护不变式用 synchronized、ReentrantLock 或更高层并发工具。
普通变量可能被线程长期读到旧值,volatile 写入会让后续读同一变量的线程看到最新写入,并建立 happens-before 关系。它解决的是“别人改了我能不能看见”的问题。
volatile 会插入或等价产生内存屏障,限制编译器和 CPU 对 volatile 读写附近指令的重排序。典型用途是双重检查锁中防止对象引用赋值先于构造初始化被其他线程看见。
i++ 包含读取、加一、写回三步,多个线程可能同时读到同一个旧值再分别写回。volatile 只能让每次读写可见,不能把这三步合成不可打断的原子操作。
如果业务要求 a 和 b 同时满足某个关系,或者先检查库存再扣减,volatile 无法保护检查和更新之间不被其他线程插入。此时需要锁、CAS 循环、数据库约束或专门的并发结构。
停止标记、配置版本号、单次发布可以用 volatile;高并发计数用 AtomicLong、LongAdder;保护临界区、组合状态和条件等待用锁或并发工具。回答时要把边界说清,而不是绝对化。
不能。i++ 是读、改、写复合操作,volatile 只能保证每次读写的可见性和顺序约束,不能阻止两个线程同时基于同一个旧值计算。
对象创建可能被重排序为分配内存、引用赋值、执行构造。没有 volatile,另一个线程可能看到非 null 引用,但对象还没初始化完成。volatile 限制这种发布重排序。
两者都能提供可见性,但 synchronized 还提供互斥和临界区边界。进入和退出锁也有内存语义,能保证锁内修改对后续获得同一把锁的线程可见。
当状态更新是单变量赋值,且不需要把读取和后续操作绑定成原子整体时通常足够,例如停止标记、配置开关、一次性发布引用。只要有复合条件或多字段一致性,就要更强同步。