volatile 和 synchronized 的区别是什么?

4 阅读3分钟

volatilesynchronized 都和 Java 并发有关,但它们解决的问题不一样。

一句话理解

  • volatile:保证可见性有序性不保证原子性
  • synchronized:保证可见性有序性,还保证原子性

1. volatile 是什么

volatile 修饰变量,意思是:

  • 一个线程修改了这个变量
  • 其他线程能够立刻看到最新值

它主要解决的是线程之间变量不可见的问题。

示例

class Demo {
    private volatile boolean flag = true;

    public void stop() {
        flag = false;
    }

    public void run() {
        while (flag) {
            // do something
        }
    }
}

如果 flag 不加 volatilerun() 所在线程可能一直读到旧值,死循环不结束。
加了 volatile 后,stop() 修改后,run() 能及时看到。


2. synchronized 是什么

synchronized 是一种同步锁机制,用于控制多个线程对共享资源的访问。

它的作用是:

  • 同一时刻只允许一个线程进入同步代码块
  • 保证共享数据操作的原子性
  • 一个线程释放锁后,其他线程能看到最新值

示例

class Counter {
    private int count = 0;

    public synchronized void incr() {
        count++;
    }

    public synchronized int getCount() {
        return count;
    }
}

这里 count++ 虽然不是原子操作,但放在 synchronized 里就安全了。


3. 核心区别

3.1 可见性

两者都能保证可见性。

  • volatile:写后立刻刷新到主内存,读时从主内存拿最新值
  • synchronized:线程进入和退出同步块时,JMM 会保证工作内存与主内存同步

3.2 原子性

这是最关键的区别。

  • volatile不能保证原子性
  • synchronized能保证原子性

例如:

volatile int count = 0;

public void incr() {
    count++;
}

count++ 实际上分三步:

  1. 读 count
  2. 加 1
  3. 写回 count

多个线程同时执行时,还是会丢失更新,所以不安全。


3.3 使用范围

  • volatile:只能修饰变量
  • synchronized:可以修饰方法代码块

3.4 性能

一般来说:

  • volatile 更轻量
  • synchronized 更重一些,因为涉及加锁和线程竞争

不过在现代 JDK 中,synchronized 已经优化得很好了,不能简单认为它一定很慢。


4. 什么时候用 volatile

适合这些场景:

场景1:状态标记

volatile boolean shutdown = false;

场景2:一个线程写,多个线程读

volatile int version;

场景3:双重检查单例中的实例变量

class Singleton {
    private static volatile Singleton instance;

    private Singleton() {}

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

这里 volatile 是为了防止指令重排序


5. 什么时候用 synchronized

适合这些场景:

  • 多线程对共享变量进行复合操作
  • 需要保证一段代码同一时刻只能一个线程执行
  • 涉及多个变量的一致性维护

例如:

class Account {
    private int balance = 100;

    public synchronized void withdraw(int money) {
        if (balance >= money) {
            balance -= money;
        }
    }
}

这里必须用 synchronized,因为“判断 + 修改”必须是一个整体。


6. 对比表

对比项volatilesynchronized
可见性支持支持
原子性不支持支持
有序性支持(禁止指令重排)支持
是否加锁不加锁加锁
使用范围变量方法、代码块
性能开销较小相对较大
适用场景状态标记、配置刷新临界区、复合操作

7. 面试常见回答模板

volatilesynchronized 都是 Java 并发控制手段。
volatile 主要保证共享变量的可见性有序性,但不能保证复合操作的原子性,所以适合做状态标记、开关变量。
synchronized 通过加锁机制保证同一时刻只有一个线程进入临界区,因此不仅保证可见性和有序性,还能保证原子性,适合保护共享资源的复合操作。
简单说,volatile轻量级的可见性保证synchronized完整的互斥同步机制


8. 一个最经典的面试陷阱

很多人会说:

volatile 能保证线程安全

这句话不完整

准确说法应该是:

volatile 只能保证单次读写的可见性和有序性,不能保证复合操作的线程安全。