volatile
关键字为什么可以保证可见性?
volatile
关键字可以保证变量的可见性,是通过 主存和线程缓存的同步机制 实现的。
具体来说,它通过 内存屏障 和 缓存一致性协议 实现了线程间变量的可见性。
可见性问题的根源
在多线程程序中,每个线程会将共享变量从主存复制到自己的 工作内存(如 CPU 缓存)中操作。
这种机制虽然提高了性能,但可能导致线程之间看到的变量值不一致的问题(即可见性问题)。
volatile
如何保证可见性
1. 修改操作强制写入主存
当一个线程修改
volatile
修饰的变量时,该变量的新值会立即刷新到主存。其他线程会被通知其缓存中的该变量值无效,必须从主存重新加载。
2. 读取操作强制从主存加载
当一个线程读取
volatile
修饰的变量时,它总是直接从主存中获取最新值,而不是从线程的本地缓存中读取。
实现机制
1. 内存屏障
volatile
的实现依赖于 内存屏障(Memory Barrier),这是一种指令,确保对变量的操作不会因编译器优化或 CPU 重排序而改变顺序。
写屏障(Store Barrier):
在写入volatile
变量之后,会插入一个写屏障,强制将最新值写回主存。读屏障(Load Barrier):
在读取volatile
变量之前,会插入一个读屏障,确保从主存加载变量值。
2. 缓存一致性协议
现代 CPU 使用缓存一致性协议(如 MESI 协议)来确保多核处理器的缓存数据一致性。
当一个线程修改
volatile
变量并刷新到主存时,其他线程缓存中该变量的值会被标记为 无效。当其他线程试图访问该变量时,会发现缓存无效,从而重新从主存加载最新值。
代码示例
以下代码展示了 volatile
如何确保变量的可见性:
1. 不使用 volatile
(可能导致可见性问题)
public class VisibilityWithoutVolatile {
private boolean flag = true;
public void writer() {
flag = false; // 修改 flag 的值
}
public void reader() {
while (flag) {
// 无限循环,可能无法感知 flag 的变化
}
System.out.println("Flag has been updated to false");
}
public static void main(String[] args) {
VisibilityWithoutVolatile example = new VisibilityWithoutVolatile();
Thread writerThread = new Thread(example::writer);
Thread readerThread = new Thread(example::reader);
readerThread.start();
writerThread.start();
}
}
问题:线程可能会一直读取旧值,因为
flag
的更新未及时刷新到主存,或者另一个线程未从主存中加载最新值。
2. 使用 volatile
(确保可见性)
public class VisibilityWithVolatile {
private volatile boolean flag = true;
public void writer() {
flag = false; // 修改 flag 的值,立即刷新到主存
}
public void reader() {
while (flag) {
// 读取时从主存获取最新值
}
System.out.println("Flag has been updated to false");
}
public static void main(String[] args) {
VisibilityWithVolatile example = new VisibilityWithVolatile();
Thread writerThread = new Thread(example::writer);
Thread readerThread = new Thread(example::reader);
readerThread.start();
writerThread.start();
}
}
结果:程序能够正常终止,因为
volatile
确保了变量的可见性。
总结
volatile
的可见性保证:修改
volatile
变量时,会立即写入主存。读取
volatile
变量时,总是从主存读取。
实现机制:
内存屏障:防止重排序,确保操作顺序。
缓存一致性协议:通知其他线程缓存失效,强制重新加载。
适用场景:
适合状态标志(如
boolean
)等简单场景。不适合需要保证原子性的场景(如
i++
)。