volatile在框架源码中主要用于修饰状态标志(如running、shutdown),确保单次写入后所有线程立即可见,适用于一写多读、无竞态、需即时可见的场景,底层通过内存屏障与MESI协议实现缓存一致性。

在框架底层源码中,volatile 通常用于修饰状态标志(如 running、shutdown、initialized 等),不参与复杂计算,只承担“开关”或“信号”作用。它的价值不是让操作变快,而是让变化“被看见”——哪怕只是改了一个布尔值,也要确保所有线程立刻感知。
volatile 在框架源码中的典型使用模式
主流框架(如 Spring、Netty、Dubbo、RocketMQ)大量采用 volatile 修饰轻量级状态变量。常见写法有:
-
单次写 + 多次读的控制标志:例如 Netty 的
AbstractChannel中的registered、active字段,初始化后仅由单线程设置,其他线程只读判断; -
启动/关闭生命周期标识:如 RocketMQ 的
DefaultMQProducerImpl中的serviceState(枚举类型),配合volatile+compareAndSet实现状态跃迁; -
避免双重检查锁(DCL)失效:Spring 的早期单例工厂中,
singletonObjects缓存未加锁时,会用 volatile 修饰内部状态字段来防止指令重排导致对象未完全构造就被其他线程看到。
为什么不用 synchronized 或 Lock?
因为状态变量的读写本身是原子的(boolean/int/long 引用类型写入在 JVM 层面保证原子性),且不需要互斥执行。加锁会带来上下文切换、阻塞等待等开销,而 volatile 仅插入内存屏障指令,CPU 级别干预缓存同步,零阻塞、低延迟。
例如 Spring 的 AbstractBeanFactory 中:private volatile boolean allowCircularReferences = true;
这个字段只在配置阶段被主线程设一次,运行时被成百上千个创建 Bean 的线程并发读取——用 volatile 正好匹配“一写多读、无竞态、需即时可见”的场景。
底层如何做到“立即可见”?看 JIT 和 CPU 协同动作
当 JVM 编译含 volatile 字段的字节码时,会在对应读写位置插入内存屏障:
你接手一个需求时,是不是经常这样:"改一个小字段 → 结果把不相关的代码也改了"、"需求说得很模糊 → 做着做着发现跑偏了"、"数据库加个字段 → 上线才发现漏了数据兼容"、"做了个新功能 → 一个月后自己都忘了当时怎么设计的"、"写到一半被中断 → 再回来不知道从哪继续";这个 Skill 就是专门解决这些问题的。 它会自动探测你的项目环境(JDK、Spring Boot、ORM、数据库、前端框架),记在
立即学习“Java免费学习笔记(深入)”;
- 写 volatile 变量前:插入 StoreStore 屏障,确保该变量写入前的所有写操作已刷新到缓存;
- 写 volatile 变量后:插入 StoreLoad 屏障,强制刷出本核缓存,并向其他 CPU 核心发送无效化请求(基于 MESI 协议);
- 读 volatile 变量前:插入 LoadLoad 屏障,先使本地缓存失效,再从主内存或最新共享缓存重新加载。
这不是“每次都访问主内存”,而是通过硬件协议触发缓存一致性同步,效果等价于“其他线程下一次读就能拿到最新值”。
注意它不能做什么:常见误用陷阱
框架作者深谙 volatile 边界,所以你几乎看不到它出现在以下位置:
-
复合操作上:比如
counter++、list.add(x)—— 这些不是原子的,volatile 无法防止读-改-写中间被抢占; - 作为锁替代品:不会用 volatile boolean 来实现“获取资源→执行→释放”逻辑,那必须用 synchronized 或 AQS;
-
修饰数组引用本身而非元素:如
volatile int[] arr只保证 arr 引用可见,arr[0] 修改仍不可见;真要保证数组元素可见,得用AtomicIntegerArray。

















