★ JMM 是什么?volatile 如何保证可见性与有序性?

结论先行:Java 内存模型(JMM)规定共享变量存在于主内存,每条线程在自己的工作内存中操作副本,并靠 happens-before 规则约束重排序。volatile 的语义有两条:写操作对其他线程的读立即可见;禁止相关指令重排序(通过插入内存屏障实现)。但它不保证原子性

三大特性与 volatile 的关系

并发特性含义volatile 能否保证
原子性操作不可再分否(如 i++ 仍非原子)
可见性一线程修改,其他线程立即可见能(写刷主存、读失效本地副本)
有序性指令按预期顺序执行能(禁止重排序 + 内存屏障)

记忆:volatile 只防“走样”,不防“拆分”——i++ 这类读-改-写操作仍需要锁或原子类兜底。

保证机制拆解

  • 可见性:volatile 写会强制把新值刷新回主内存;volatile 读会强制从主内存读取,使其他线程的写入立即可见。
  • 内存屏障:volatile 写前插入 StoreStore、写后插入 StoreLoad;读操作前后配合 LoadLoad/LoadStore,划出重排序禁区。
  • happens-before 规则:对 volatile 变量的写,happens-before 后续对该变量的每一次读。
  • 典型应用:状态标志位、单例双重检查锁、发布不可变对象;计数类读改写请交给 Atomic 或锁。
public class VolatileDemo {
    private static volatile boolean stop = false; // 保证修改对其他线程可见

    public static void main(String[] args) throws Exception {
        Thread worker = new Thread(() -> {
            while (!stop) { /* 忙等;若无 volatile 可能永远读到旧值 */ }
            System.out.println("stopped");
        });
        worker.start();
        Thread.sleep(100);
        stop = true; // 不声明 volatile,主线程的修改可能不被 worker 感知
    }
}

常见追问 / 记忆点

  • 记忆点:volatile = 可见性 + 有序性,缺原子性;经典场景是状态标志与单例双重检查锁。
  • 追问:双重检查锁单例的 instance 必须 volatile,否则可能拿到“半初始化”的对象。
  • 追问:synchronized 三者都保证;Lock 通过其内存语义同样能保证可见性,只是写法更繁琐。
  • 追问:final 字段有额外内存语义——构造完成后对其他线程可见,常与 volatile 互补使用。
笔记加载中…