volatile 和 synchronized 的区别
1 核心定位
- volatile :变量修饰符,只保证可见性、禁止指令重排,不保证原子性
- synchronized :同步锁,保证可见性 + 禁止重排 + 原子性(互斥),可以修饰方法、代码块
2 多维度对比
| 特性 | volatile | synchronized |
|---|---|---|
| 原子性 | ❌ 不保证,复合操作(i++)不安全 | ✅ 保证,同一时刻只有一个线程执行 |
| 可见性 | ✅ 强制刷新主存,变量修改立刻对其他线程可见 | ✅ 锁释放时刷新主存,也保证可见性 |
| 有序性(指令重排序) | ✅ 禁止重排序 | ✅ 锁块内禁止越界重排序 |
| 作用对象 | 只能修饰成员变量 | 修饰方法 / 代码块,作用于对象锁 |
| 阻塞 | 不会阻塞线程 | 会阻塞,竞争不到锁线程等待 |
| 性能 | 开销很小,无锁 | 开销大,涉及锁竞争、内核态切换 |
| 适用场景 | 读多写少,状态标记位 | 多线程读写、计数、复合操作 |
2.1 volatile
重点:不保证原子性
i++ 分为三步:读i → +1 → 写回
volatile只能保证读到最新值,不能把三步打包成原子,多线程下依然会出现计数错误。
java
volatile int i = 0;
i++; // 线程不安全!
volatile典型使用场景:状态开关标记
java
volatile boolean stop = false;
//线程1
while(!stop){ ... }
//线程2修改stop=true,其他线程立刻感知
2.2 synchronized
- 底层:对象监视器monitor,加锁、解锁。
- 加锁:获取monitor,清空本地缓存,从主存读取变量。
- 释放锁:把本地缓存修改刷新回主存。
- 锁把整个代码块变成原子,多线程不会交错执行。
java
synchronized (obj){
i++; // 线程安全
}
3 底层原理简述
-
volatile :内存屏障(memory barrier)
写volatile后插入写屏障,强制把CPU缓存数据刷回主内存(内存条RAM);读volatile前插入读屏障,强制从主内存读取,阻止CPU指令重排。不加锁。
-
synchronized :monitor锁
偏向锁→轻量级锁(CAS)→重量级锁(操作系统互斥锁),有锁升级过程。既做互斥,又通过内存屏障保证可见性与重排序。
4 总结
volatile 是轻量级的,只解决可见性、禁止重排,不能保证原子性,适合布尔标记;
synchronized 是锁,原子、可见、防重排全包,会阻塞,适合复合读写操作。
5 常见坑
- 不要用volatile做计数器 i++,结果错乱。
- volatile不能修饰局部变量。
- synchronized不仅加互斥锁,同样自带可见性,不需要再搭配volatile。