1. volatile 关键字的作用
volatile 关键字的主要作用是保证可见性和有序性,禁止编译器优化。
- 保证可见性:当一个变量被声明为 volatile 之后,每次读取这个变量的值都会从主内存中读取,而不是从缓存中读取,这就保证了不同线程对这个变量操作的可见性。
- 有序性:volatile 关键字保证了不同线程对一个 volatile 变量的读写操作的有序性。
- 禁止编译器优化:编译器会对代码进行各种优化来提高性能,但是这些优化也可能让同步代码失效。volatile 关键字告诉编译器不要对这段代码做优化,从而避免一些不正确的优化。
2. volatile 的底层原理
volatile 关键字底层原理依赖于内存屏障和缓存一致性协议。
- 内存屏障:内存屏障会强制让读和写操作都访问主内存,从而实现可见性。volatile 写操作后会加入写屏障,volatile 读操作前会加入读屏障。
- 缓存一致性协议:每个处理器都有自己的高速缓存,当某个处理器修改了共享变量,需要缓存一致性协议来保证其他处理器也看到修改后的值。缓存一致性协议会在读操作后和写操作前加入缓存刷新操作,保证其他处理器的缓存是最新值。
3. volatile 的使用案例
volatile 关键字常用在 DCL(Double Check Lock)单例模式中:
public class Singleton {
private volatile static Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
复制代码
这里使用 volatile 是为了防止指令重排序,保证 instance 初始化后其他线程可以看到。
volatile 也常用在Interruptible线程中,实现线程的中断功能:
public class InterruptibleThread extends Thread {
private volatile boolean interrupted = false;
public void interrupt() {
interrupted = true;
}
@Override
public void run() {
while (!interrupted) {
// do something
}
}
}
复制代码
这里 volatile 可以保证 interrupted 的可见性,使线程立即响应中断调用。
4. volatile 的原子性问题
volatile 关键字只能保证可见性和有序性,不能保证原子性。
对一个 volatile 变量的读写操作并不是原子的,而是可以分为读、改、写三个操作:
- 读: 读取 volatile 变量的值
- 改:对值进行修改
- 写:将修改后的值写入 volatile 变量
这三个操作并不是一个原子操作,在多线程环境下可能导致数据竞争问题:
public class VolatileNoAtomicDemo {
private volatile int counter = 0;