一、前言
- volatile是Java中作用于多线程并发环境下的核心关键字。
- 其作用是确保共享变量的可见性和有序性,但不保证原子性。
二、保证可见性(Visibility)
- 当变量被volatile修饰时,所有线程对该变量的修改会强制立即刷回主内存;
- 其他线程每次读取时也会强制从主内存重新加载,确保所有线程看到的变量值是最新的。
- 代码示例(无volatile导致死循环):
public class VisibilityDemo {
private volatile boolean flag = true;
public void runTask() {
new Thread(() -> {
System.out.println("子线程启动");
while (flag) { }
System.out.println("子线程停止");
}).start();
}
public static void main(String[] args) throws InterruptedException {
VisibilityDemo demo = new VisibilityDemo();
demo.runTask();
Thread.sleep(1000);
demo.flag = false;
System.out.println("主线程修改完成");
}
}
- 结果说明:
- 无volatile时:子线程可能因缓存未更新而陷入死循环。
- 有volatile时:子线程能立即感知到flag变化,正常退出。
三、禁止指令重排序(Ordering)
- volatile通过内存屏障(Memory Barrier) 禁止编译器和处理器对代码执行顺序的优化;
- 确保多线程环境下关键操作的执行顺序符合预期。
- 代码示例(单例模式双重检查锁):
public 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;
}
}
- 关键点:
- 在单例模式中,使用volatile来禁止指令重排序,从而避免未完全初始化的对象被使用。
- 若instance不加volatile,其他线程可能拿到未完成初始化的对象(半初始化状态)。
- volatile确保new Singleton()的指令顺序不被重排。
四、volatile的局限性
public class AtomicityDemo {
private volatile int count = 0;
public void increment() {
count++;
}
}
- 问题:多线程并发执行count++时,最终结果可能小于预期。
- 解决方案:改用AtomicInteger或synchronized。
五、适用场景
- 状态标志位:如开关控制(flag变量)。
- 单次安全发布:确保对象初始化完成后再被引用(如单例模式)。
- 独立观察:变量不依赖其他状态(如定期更新的温度值)。
六、拓展
1.底层原理
- 可见性:通过CPU的缓存一致性协议(如MESI)强制同步主内存数据。
- 禁止重排序:在读写操作前后插入LoadStore/StoreStore等内存屏障指令。
2.和synchronized的区别
- 性能:
- volatile 是一种轻量级的同步机制,开销较小,但它只能用于变量的可见性和禁止重排序,无法实现复杂的同步逻辑。
- synchronized则是重量级的同步机制,可以保证代码块的原子性和可见性,但开销较大。
- 使用场景:
- volatile 适用于简单的状态标志、标记等场景。
- 而 synchronized 更适合复杂的临界区保护,需要确保多个操作的原子性时。
七、总结
- volatile是轻量级同步机制,适用于需要保证可见性和有序性场景,但需结合原子类或锁处理复合操作。