volatile与synchronized的区别详解

在Java并发编程中,volatilesynchronized都是用于处理多线程环境下共享变量访问的关键字,但它们的工作机制和适用场景有显著区别。下面我将从多个维度详细对比这两者的差异。

1. 基本概念对比

特性volatilesynchronized
作用范围变量级别代码块或方法级别
原子性不保证复合操作的原子性保证整个代码块的原子性
可见性保证变量的可见性保证变量的可见性
有序性禁止指令重排序保证有序性
线程阻塞不会导致线程阻塞可能导致线程阻塞
性能更高相对较低

2. 内存语义与实现原理

2.1 volatile的内存语义

public class VolatileExample {
    private volatile boolean flag = false;
    
    public void writer() {
        flag = true;  // 写volatile变量
    }
    
    public void reader() {
        if (flag) {   // 读volatile变量
            // do something
        }
    }
}

实现原理

  1. 写操作:当写一个volatile变量时,JMM会把该线程对应的本地内存中的共享变量值立即刷新到主内存
  2. 读操作:当读一个volatile变量时,JMM会把该线程对应的本地内存置为无效,直接从主内存读取

内存屏障

  • 在每个volatile写操作前插入StoreStore屏障
  • 在每个volatile写操作后插入StoreLoad屏障
  • 在每个volatile读操作后插入LoadLoad和LoadStore屏障

2.2 synchronized的内存语义

public class SynchronizedExample {
    private int count = 0;
    
    public synchronized void increment() {
        count++;  // 原子操作
    }
    
    public void doSomething() {
        synchronized(this) {
            // 同步代码块
        }
    }
}

实现原理

  1. 进入同步块前:先清空工作内存→从主内存拷贝变量副本
  2. 执行同步代码:使用工作内存中的变量副本
  3. 退出同步块前:将工作内存中的变量值刷新到主内存

锁机制

  • 基于Monitor实现(对象头中的Mark Word)
  • 包含偏向锁、轻量级锁、重量级锁的升级过程

3. 可见性保证对比

3.1 volatile的可见性

volatile变量的写操作对于其他线程的读操作是立即可见的。这是因为:

  1. 写操作后会强制刷新到主内存
  2. 读操作前会强制从主内存重新加载
  3. 防止编译器对指令的重排序

3.2 synchronized的可见性

synchronized块提供的可见性保证更强:

  1. 进入同步块前:会清空工作内存,从主内存重新加载变量
  2. 退出同步块时:会把工作内存中的变量强制刷新到主内存
  3. 遵循"对一个变量解锁前,必须先把此变量同步回主内存"规则

4. 原子性保证对比

4.1 volatile的非原子性

volatile不能保证复合操作的原子性。例如:

private volatile int count = 0;

// 线程不安全!
public void increment() {
    count++;  // 实际上是read-modify-write三步操作
}

count++操作实际上分为三步:

  1. 读取count的值
  2. 将值加1
  3. 写回count

在多线程环境下,这三步操作可能被其他线程打断。

4.2 synchronized的原子性

synchronized可以保证代码块的原子性:

private int count = 0;

public synchronized void increment() {
    count++;  // 原子操作
}

同一时间只有一个线程能执行同步方法/代码块,因此可以保证复合操作的原子性。

5. 有序性保证对比

5.1 volatile的有序性

volatile通过内存屏障禁止指令重排序:

  1. 写操作前的指令不会被重排序到写操作之后
  2. 读操作后的指令不会被重排序到读操作之前

典型应用:单例模式的双重检查锁定

class Singleton {
    private static volatile Singleton instance;
    
    public static Singleton getInstance() {
        if (instance == null) {
            synchronized(Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

5.2 synchronized的有序性

synchronized保证:

  1. 同步块内的指令不会被重排序到同步块外
  2. 同步块内的指令可以重排序,但不会影响执行结果(as-if-serial语义)

6. 性能对比

指标volatilesynchronized
上下文切换有可能导致线程阻塞和切换
编译优化较少限制较多限制
适用场景单一变量的可见性控制复杂操作的原子性控制

volatile的性能通常比synchronized高,因为:

  1. 不会导致线程阻塞
  2. 没有锁的获取和释放开销
  3. JVM对volatile的优化更好

7. 典型使用场景

7.1 volatile适用场景

  1. 状态标志位:
volatile boolean shutdownRequested;

public void shutdown() {
    shutdownRequested = true;
}

public void doWork() {
    while (!shutdownRequested) {
        // 执行任务
    }
}
  1. 一次性安全发布:
class Resource {
    private volatile static Resource instance;
    
    public static Resource getInstance() {
        if (instance == null) {
            instance = new Resource();
        }
        return instance;
    }
}
  1. 独立观察:
volatile double temperature;

// 多个线程读取温度值,一个线程更新温度值

7.2 synchronized适用场景

  1. 复合操作:
private int counter;

public synchronized void increment() {
    counter++;
}
  1. 需要互斥访问的资源:
public class BankAccount {
    private double balance;
    
    public synchronized void deposit(double amount) {
        balance += amount;
    }
    
    public synchronized void withdraw(double amount) {
        balance -= amount;
    }
}
  1. 对象控制流:
public class BoundedBuffer {
    private final Object lock = new Object();
    private int count, putIndex, takeIndex;
    private Object[] items = new Object[100];
    
    public void put(Object x) throws InterruptedException {
        synchronized(lock) {
            while (count == items.length)
                lock.wait();
            items[putIndex] = x;
            if (++putIndex == items.length) putIndex = 0;
            ++count;
            lock.notifyAll();
        }
    }
}

8. 选择建议

根据需求选择合适的关键字:

使用volatile当

  • 变量只有一个线程写,多个线程读
  • 变量不参与不变式约束
  • 变量不需要与其他变量共同参与原子操作
  • 只需要保证变量的可见性

使用synchronized当

  • 操作需要原子性保证
  • 多个变量需要作为一个原子单元操作
  • 需要控制代码块的执行顺序
  • 需要实现线程间的协作(wait/notify)

9. 常见误区

  1. 认为volatile能替代synchronized

    • volatile不能保证复合操作的原子性
    • 例如i++这样的操作仍需同步
  2. 过度使用synchronized

    • 在不必要时使用会导致性能下降
    • 应考虑更轻量级的并发控制方式
  3. 忽视volatile的有序性

    • volatile不仅能保证可见性,还能防止指令重排序

10. 组合使用示例

在实际开发中,两者经常组合使用:

public class CheesyCounter {
    private volatile int value;
    private final Object lock = new Object();
    
    public int getValue() {
        return value;  // volatile读
    }
    
    public void increment() {
        synchronized(lock) {
            value++;  // 同步保证原子性
        }
    }
}

这种组合:

  • 读操作使用volatile保证可见性
  • 写操作使用synchronized保证原子性
  • 兼顾了性能和正确性

总结

volatile和synchronized是Java并发编程中的基础构建块,理解它们的区别对于编写正确且高效的多线程程序至关重要。volatile提供了轻量级的可见性和有序性保证,而synchronized提供了更重量级但更全面的原子性、可见性和有序性保证。开发者应根据具体场景选择合适的关键字,有时还需要组合使用它们以达到最佳效果。
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

北辰alk

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值