volatile 和 synchronized 的核心区别在于 volatile 仅保证可见性和有序性,不保证原子性且不能阻塞线程;而 synchronized 是重量级锁,既保证可见性和有序性,又通过互斥机制保证原子性。在实现上,volatile 依赖 cpu 的内存屏障指令防止指令重排序,synchronized 则依赖 jvm 的监视器锁(monitor)进行线程同步。选择时,若只需状态共享无需复合操作,用 volatile;若涉及临界区或复杂逻辑,必须用 synchronized。

volatile 如何保证可见性与有序性
volatile 通过插入内存屏障(Memory Barrier)来禁止指令重排序,并利用缓存一致性协议(如 MESI)确保变量修改对所有线程立即可见。当线程写入 volatile 变量时,会刷新到主内存;读取时,会强制从主内存加载最新值。这种机制开销极小,适合简单的标志位或单例模式的双重检查锁定。

public class VolatileExample {
private volatile boolean flag = false;
public void setFlag() {
this.flag = true; // 写操作:刷新到主存,禁止重排序
}
public void checkFlag() {
if (this.flag) { // 读操作:从主存加载,禁止重排序
System.out.println("Flag is true");
}
}
}
synchronized 如何保证原子性与互斥
synchronized 通过获取对象监视器(Monitor)来实现互斥访问。同一时刻只有一个线程能持有锁,其他线程阻塞等待。这保证了代码块内操作的原子性,同时通过锁的释放-获取关系建立 happens-before 原则,确保可见性。其代价是线程切换和阻塞带来的性能损耗,适用于复杂的临界区保护。
立即学习“Java免费学习笔记(深入)”;

public class SyncExample {
private int count = 0;
public synchronized void increment() {
count++; // 原子操作:读-改-写被锁保护
}
public synchronized int getCount() {
return count; // 保证读取的是最新值
}
}
为什么 volatile 不能替代 synchronized 处理复合操作
volatile 无法解决竞态条件,因为像 count++ 这样的复合操作包含读取、修改、写入三个步骤,非原子性。即使使用 volatile,多个线程同时执行仍可能导致数据不一致。此时必须使用 synchronized 或原子类(如 AtomicInteger)来确保操作的原子性。例如,两个线程同时递增 volatile 变量,最终结果可能小于预期值,因为中间状态可能被覆盖。


















