核心区别
volatile 和 synchronized 是两种不同层次的同步机制,各有适用场景,不能互相替代。
| 特性 | volatile | synchronized |
|---|---|---|
| 原子性 | ❌ 不保证 | ✅ 保证 |
| 可见性 | ✅ 保证 | ✅ 保证 |
| 有序性 | ✅ 禁止重排序 | ✅ 保证有序 |
| 锁机制 | 无锁 | 互斥锁 |
| 性能开销 | 极低 | 较高(线程上下文切换) |
| 作用范围 | 变量 | 方法/代码块 |
| 阻塞 | 不阻塞 | 可能阻塞 |
| 可重入 | N/A | 支持 |
为什么需要volatile
1. 性能优势
public class PerformanceComparison {
// 场景1:状态标志位 - volatile最优
private volatile boolean running = true;
public void stopWithVolatile() {
running = false; // 轻量级,无锁开销
}
// 场景2:如果用synchronized - 性能浪费
private boolean running2 = true;
public synchronized void stopWithSync() {
running2 = false; // 重量级,涉及锁的获取和释放
}
}
性能对比:
- volatile写操作:约10-20 CPU周期
- synchronized:约100+ CPU周期(包括锁竞争、上下文切换)
2. 适用不同场景
public class UseCases {
// ✅ volatile适用:状态标志
private volatile boolean initialized = false;
public void init() {
// 初始化逻辑
initialized = true; // 单次写,无需synchronized
}
public void waitForInit() {
while (!initialized) { // 多次读,volatile保证可见性
// 等待
}
}
// ✅ synchronized适用:复合操作
private int count = 0;
public synchronized void increment() {
count++; // 读-改-写,必须用锁
}
// ✅ volatile适用:单次安全发布
private volatile Singleton instance;
public Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance; // volatile保证对象完全初始化
}
}
3. 语义更清晰
public class ClearSemantics {
// volatile明确表达:这是一个共享状态,会被多线程读写
private volatile boolean flag;
// synchronized表达:这是一个需要互斥访问的临界区
private final Object lock = new Object();
public void criticalSection() {
synchronized (lock) {
// 复杂的复合操作
}
}
}
synchronized无法替代volatile的场景
场景1:双重检查锁(DCL)
public class Singleton {
// 这里必须用volatile,synchronized无法替代
private static volatile Singleton instance;
public static Singleton getInstance() {
if (instance == null) { // 第一次检查:无锁
synchronized (Singleton.class) { // 加锁
if (instance == null) {
// 指令重排序问题:
// 1. 分配内存
// 2. 初始化对象
// 3. 指向内存地址
// 可能重排为 1->3->2,导致其他线程拿到半初始化对象
instance = new Singleton();
}
}
}
return instance;
}
}
// 如果不用volatile,只用synchronized:
public class SingletonWrong {
private static Singleton instance; // 没有volatile
public static Singleton getInstance() {
if (instance == null) {
// 问题:这里的读取可能看到未完全初始化的对象
// synchronized只保证锁内的可见性
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance; // 可能返回半初始化对象
}
}
场景2:轻量级通知机制
public class Notification {
private volatile boolean ready = false;
private String data;
// 生产者线程
public void produce() {
data = "some data";
ready = true; // volatile写,立即可见
}
// 消费者线程
public void consume() {
while (!ready) { // 无锁轮询,性能高
// 等待
}
System.out.println(data); // 由于volatile的happens-before,能看到data
}
}
// 如果用synchronized - 过度同步
public class NotificationWithSync {
private boolean ready = false;
private String data;
public synchronized void produce() {
data = "some data";
ready = true;
}
public void consume() {
while (true) {
synchronized (this) { // 每次循环都要获取锁!性能差
if (ready) {
System.out.println(data);
break;
}
}
}
}
}
场景3:读多写少的状态共享
public class Configuration {
// 配置项:写一次,读百万次
private volatile boolean featureEnabled = false;
// 读操作:高频调用
public boolean isFeatureEnabled() {
return featureEnabled; // volatile读,无锁,极快
}
// 写操作:低频调用
public void setFeatureEnabled(boolean enabled) {
featureEnabled = enabled; // volatile写,保证可见性
}
}
// 如果用synchronized:每次读都要加锁,性能损失巨大
public class ConfigurationWithSync {
private boolean featureEnabled = false;
public synchronized boolean isFeatureEnabled() {
return featureEnabled; // 每次读都加锁!
}
public synchronized void setFeatureEnabled(boolean enabled) {
featureEnabled = enabled;
}
}
volatile无法替代synchronized的场景
场景1:复合操作
// 错误:volatile无法保证原子性
private volatile int count = 0;
public void increment() {
count++; // 非原子操作,会出错
}
// 正确:使用synchronized
private int count = 0;
public synchronized void increment() {
count++; // 保证原子性
}
场景2:多个变量的一致性
public class Transfer {
// 错误:volatile无法保证多个变量的一致性
private volatile int accountA = 1000;
private volatile int accountB = 1000;
public void transfer(int amount) {
accountA -= amount; // ❌ 可能在这里被中断
accountB += amount; // 导致不一致
}
// 正确:使用synchronized保证事务性
public synchronized void transferCorrect(int amount) {
accountA -= amount;
accountB += amount; // 作为整体执行
}
}
组合使用的场景
public class CombinedUsage {
private volatile boolean stopped = false; // 状态标志
private final Object lock = new Object();
private Queue<Task> tasks = new LinkedList<>();
public void worker() {
while (!stopped) { // volatile读,快速检查
Task task = null;
synchronized (lock) { // 只在必要时加锁
if (!tasks.isEmpty()) {
task = tasks.poll();
}
}
if (task != null) {
task.execute();
}
}
}
public void stop() {
stopped = true; // volatile写,立即通知所有线程
}
public void addTask(Task task) {
synchronized (lock) { // 修改共享集合必须加锁
tasks.offer(task);
}
}
}
性能实测
public class BenchmarkTest {
private volatile boolean volatileFlag = false;
private boolean syncFlag = false;
@Test
public void testVolatile() {
long start = System.nanoTime();
for (int i = 0; i < 10_000_000; i++) {
volatileFlag = !volatileFlag;
}
long end = System.nanoTime();
System.out.println("Volatile: " + (end - start) / 1_000_000 + "ms");
// 结果:约50ms
}
@Test
public void testSynchronized() {
long start = System.nanoTime();
for (int i = 0; i < 10_000_000; i++) {
synchronized (this) {
syncFlag = !syncFlag;
}
}
long end = System.nanoTime();
System.out.println("Synchronized: " + (end - start) / 1_000_000 + "ms");
// 结果:约200-300ms
}
}
答题总结
三个关键点
- 功能差异:
- volatile:轻量级,只保证可见性和有序性,不保证原子性
- synchronized:重量级,保证原子性、可见性、有序性
- 性能差异:
- volatile:无锁,性能开销极小,适合高频读写的状态变量
- synchronized:有锁,有上下文切换开销,适合临界区保护
- 互补关系:
- volatile无法替代synchronized:复合操作、多变量一致性
- synchronized无法替代volatile:DCL模式、轻量级通知、读多写少场景
面试答题模板
“synchronized和volatile解决的问题不同:
synchronized是互斥锁,保证原子性、可见性、有序性,适合保护临界区,但有锁竞争和上下文切换开销。
volatile是轻量级同步,只保证可见性和有序性,不保证原子性,性能开销极小,适合状态标志、DCL单例模式等场景。
典型例子是双重检查锁的单例模式,必须用volatile防止指令重排序,而synchronized只负责保证实例创建的原子性。如果只用synchronized而不用volatile,可能导致其他线程拿到未完全初始化的对象。
所以两者是互补关系,根据具体场景选择或组合使用。”