volatile 与 synchronized 的区别
| volatile | synchronized | |
|---|---|---|
| 可见性 | 保证 | 保证 |
| 有序性 | 禁止相关的指令重排序 | 保证 |
| 原子性 | 只保证单次读写,count++ 这类复合操作不保证 | 保证临界区整体的原子性 |
| 互斥 | 没有 | 有,同一时刻只有一个线程持有锁 |
为什么 volatile 不能用来计数
count++ 实际上是"读、加一、写回"三步。两个线程可能读到同一个旧值,各自加一后写回,结果只增加了一次。计数应使用 AtomicInteger,高并发累加可以用 LongAdder。
happens-before 的两条规则
- 对一个 volatile 变量的写,happens-before 之后对它的读。
- 对一个锁的解锁,happens-before 之后对同一个锁的加锁。
双重检查锁定为什么要加 volatile
public 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;
}
}
instance = new Singleton() 大致分为分配内存、初始化对象、把引用赋给变量三步,后两步可能被重排序。没有 volatile 时,另一个线程可能在第一次判断时看到一个非空、但还没初始化完的对象。
怎么选
- 只是一个状态标志,例如
volatile boolean running:用 volatile。 - 多个变量要一起更新,或者有"先检查后执行"的逻辑:用 synchronized 或显式锁。
- JVM 对 synchronized 做了很多优化,例如锁消除、锁粗化、轻量级锁,无竞争时开销很小,不必为了性能刻意回避。