volatile关键字介绍-Java
volatile
是 Java 中用于修饰变量的关键字,常用于多线程编程。它是一个轻量级的同步机制,用来确保共享变量在多个线程之间的可见性,并防止指令重排序。
1. volatile 的特性
volatile
修饰的变量具备以下两个关键特性:
1.可见性(Visibility):
- 当一个线程修改了 volatile 修饰的变量,修改后的值会立刻被刷新到主内存。
- 其他线程读取这个变量时,会直接从主内存中读取最新值,而不是从线程的工作内存中读取旧值。效果:所有线程对 volatile 变量的访问是最新的。
2.禁止指令重排序(Orderliness):
- 在 volatile 变量的读写操作前后,编译器和处理器不会对代码进行重排序。
- 效果:确保对 volatile 变量的操作顺序符合程序的逻辑预期。
2. volatile 的实现原理
(1) 内存可见性原理
- Java 内存模型(JMM):
- Java 使用主内存(Main Memory)和线程工作内存(Working Memory)来管理共享变量。
- 通常,线程会将共享变量从主内存拷贝到自己的工作内存中,进行操作后再写回主内存。
- volatile 保证变量的修改会立即刷新到主内存,同时禁止线程从工作内存中读取过期数据。
(2) 指令重排序规则
- 编译器和 CPU 在执行程序时,可能会出于性能优化对指令进行重排序。
- 使用
volatile
修饰变量后,读写操作会添加内存屏障(Memory Barrier),防止指令重排序。 - 内存屏障是一种 CPU 指令,确保屏障前的所有操作在屏障之后的操作之前完成。
3. volatile 的使用场景
(1) 状态标志变量
volatile
常用于表示线程间的状态,例如停止标志。
public class VolatileFlag { private volatile boolean running = true; public void stop() { running = false; // 修改 running 状态 } public void execute() { while (running) { // 执行任务 } } }
- 这里的
volatile
保证了running
对所有线程的可见性。
(2) 单例模式的双重检查锁
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; } }
- 这里的
volatile
防止实例化过程中指令重排序导致的线程安全问题。
(3) 配合非阻塞算法volatile
可以在某些非阻塞算法中保证共享变量的可见性。例如,计数器递增:
public class Counter { private volatile int count = 0; public void increment() { count++; } }
注意:
count++
不是原子操作,此处仅展示可见性。如果需要原子性,需使用AtomicInteger
。
4. volatile 的局限性
- 不保证原子性:volatile 只能保证变量的可见性,不能保证操作的原子性。
- 仅适用于单个变量:volatile 只能保证单个变量的可见性,对于复合操作(如 i++)或多个变量的操作不提供安全性。
- 性能开销:使用 volatile 会降低一定的性能,因为它需要频繁地刷新主内存,导致缓存失效。
5. volatile 与 synchronized 的对比
特性 | volatile | synchronized |
功能 | 仅保证可见性,不能保证原子性 | 同时保证可见性和原子性 |
锁机制 | 无锁机制,适合轻量级操作 | 内置锁,适合复杂的线程同步 |
性能 | 性能较高,适合简单场景 | 性能较低(JDK 1.6 以后优化性能较好) |
适用场景 | 状态标志、双重检查锁 | 复杂的线程同步场景 |
是否阻塞线程 | 非阻塞 | 阻塞线程 |
6. volatile 与 Atomic 的对比
特性 | volatile | Atomic 类 |
功能 | 仅保证可见性,不能保证原子性 | 保证可见性和原子性 |
实现原理 | 使用内存屏障(Memory Barrier) | 使用 CAS(Compare-And-Swap) |
适用场景 | 状态标志、简单读写 | 计数器、复合操作 |
7. 关键点总结
- 何时使用
volatile
: - 当变量需要被多个线程共享,并且是单个变量的简单状态标志时(如停止标志、状态标识)。
- 不涉及复合操作(如 x++ 或 x = x + 1)。
- 何时不适用
volatile
: - 如果需要保证操作的原子性(如计数器递增),则 volatile 不适合。
- 涉及多个变量之间的同步或复合操作时,volatile 无法解决问题,需要使用 synchronized 或其他并发工具。
Java碎碎念 文章被收录于专栏
来一杯咖啡,聊聊Java的碎碎念呀