volatile只保证单变量可见性与禁止重排序,synchronized保障代码块的原子性、可见性与有序性;二者各司其职,不可替代,仅dcl单例中存在明确分工的必要组合。

volatile 和 synchronized 并不是“配合使用”的关系,而是各司其职、不可互相替代的两种机制。它们解决的问题不同、作用范围不同、底层语义也不同。强行混用不仅没有好处,反而容易引发理解偏差或掩盖设计缺陷。
volatile 的定位:只管“单变量”的可见性与重排序
它适用于一个线程写、多个线程读的简单状态场景:
- 控制线程生命周期的标志位,如 private volatile boolean running = true;
- 双重检查锁(DCL)单例中的实例引用,如 private static volatile Singleton instance;——这里 volatile 的唯一作用是防止 new Singleton() 指令被重排序,导致其他线程看到未初始化完成的对象
- 一次性发布操作,比如初始化配置后通知其他线程生效
注意:volatile 对任何复合操作(如 count++、list.add() 后再更新 size)完全无效,也不参与锁竞争、不阻塞线程。
synchronized 的定位:保护“代码段”的原子性+可见性+有序性
它用于需要多步操作不可分割、或涉及多个变量协同更新的临界区:
- 计数器自增:synchronized(this) { count++; }
- 银行转账:synchronized(accountLock) { from.balance -= amount; to.balance += amount; }
- 集合操作加状态维护:synchronized(list) { list.add(x); size++; }
进入 synchronized 块时,线程会从主内存重新加载所有共享变量;退出时,会把修改强制刷回主内存。这个过程天然包含可见性保障,不需要额外加 volatile。
唯一被广泛认可的“组合”:DCL 单例中的 volatile + synchronized
这是唯一有明确分工、不可省略双方的典型用法:
- volatile 保证 instance 引用本身的可见性与构造指令不重排
- synchronized 保证创建对象的动作只执行一次,且是原子的
- 第一次 null 检查在锁外,避免每次调用都加锁;第二次检查在锁内,防止多个线程同时通过第一层检查后重复创建
除此之外,像 “volatile int counter; synchronized(this) { counter++; }” 这类写法毫无意义——synchronized 已经确保了 counter 的读写可见性,volatile 是冗余的,还会误导后续维护者。
更现代、更推荐的替代方案
如果目标是高性能原子操作,优先考虑 java.util.concurrent.atomic 包:
- AtomicInteger 替代 volatile int + synchronized 自增
- AtomicReference 替代 volatile 引用 + 手动同步初始化
- 它们内部基于 CAS 实现,兼具原子性与可见性,比锁更轻量,也比单纯 volatile 更安全
不复杂但容易忽略:选对工具的前提,是先分清你要解决的是可见性问题、原子性问题,还是两者都要。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











