volatile仅保证变量的可见性和有序性,不保证原子性;synchronized则通过锁机制同时保障原子性、可见性和有序性;二者唯一合理共存场景是双重检查锁单例模式中分工协作。

volatile 和 synchronized 并不是常规意义上的“结合使用”关系,它们解决的是不同层次的并发问题,强行混用往往画蛇添足。真正需要二者共存的场景极少,且有明确边界——目前被广泛验证、语义清晰的唯一典型,是双重检查锁(DCL)单例模式中的配合。
volatile 解决什么?只管“变量本身”的可见与顺序
它确保:写操作立即刷回主内存,读操作强制从主内存加载;同时禁止 JVM 对该变量的读写指令重排序。但它不保证任何复合操作的原子性。
- 适合单线程写、多线程读的状态标志,比如:
private volatile boolean shutdown = false; - 适合 DCL 中防止
new Singleton()的指令重排(如:分配内存→初始化→赋值),避免其他线程拿到未构造完成的对象引用 - 不适合
count++、list.add(x)这类含读-改-写的操作——即使加了 volatile,结果依然可能出错
synchronized 解决什么?锁定“一段逻辑”的完整执行
它通过 Monitor 锁实现互斥,天然保障三件事:代码块内操作不可打断(原子性)、进入时重载最新值、退出时刷新所有修改(可见性)、临界区内部指令不被跨线程重排(有序性)。
- 适合多步协同操作,比如银行转账:
accountA.withdraw(100); accountB.deposit(100); - 适合对多个变量统一更新,或需依赖中间状态判断的逻辑
- 一旦用 synchronized 包裹了某变量的全部读写,再给该变量加 volatile 是冗余的——锁机制已覆盖其可见性和有序性需求
唯一合理共存的场景:DCL 单例中的分工协作
这里 volatile 和 synchronized 各司其职,缺一不可:
- volatile 修饰 instance:防止 new 操作重排,确保其他线程看到的要么是 null,要么是完全构造好的对象
- synchronized 保护初始化块:保证最多只有一个线程能执行构造逻辑,避免重复创建
- 两次 null 检查分工明确:第一次无锁快速判断,第二次加锁后再次确认,兼顾性能与安全
常见错误:把 volatile 当成 synchronized 的“轻量补充”
例如这样写毫无意义:
private volatile int counter;public void increment() {
synchronized (this) { counter++; }
}
counter 已被 synchronized 完全保护,volatile 不仅多余,还传递错误信号——让人误以为“这个变量特殊”,实则混淆了同步层级。JVM 可能优化掉该 volatile 语义,但代码可维护性已受损。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











