volatile 和 synchronized 都和 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 不加 volatile,run() 所在线程可能一直读到旧值,死循环不结束。
加了 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++ 实际上分三步:
- 读 count
- 加 1
- 写回 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. 对比表
| 对比项 | volatile | synchronized |
|---|---|---|
| 可见性 | 支持 | 支持 |
| 原子性 | 不支持 | 支持 |
| 有序性 | 支持(禁止指令重排) | 支持 |
| 是否加锁 | 不加锁 | 加锁 |
| 使用范围 | 变量 | 方法、代码块 |
| 性能开销 | 较小 | 相对较大 |
| 适用场景 | 状态标记、配置刷新 | 临界区、复合操作 |
7. 面试常见回答模板
volatile 和 synchronized 都是 Java 并发控制手段。
volatile 主要保证共享变量的可见性和有序性,但不能保证复合操作的原子性,所以适合做状态标记、开关变量。
synchronized 通过加锁机制保证同一时刻只有一个线程进入临界区,因此不仅保证可见性和有序性,还能保证原子性,适合保护共享资源的复合操作。
简单说,volatile 是轻量级的可见性保证,synchronized 是完整的互斥同步机制。
8. 一个最经典的面试陷阱
很多人会说:
volatile 能保证线程安全
这句话不完整。
准确说法应该是:
volatile 只能保证单次读写的可见性和有序性,不能保证复合操作的线程安全。