🌆 故事背景:信息同步的魔法公告板
想象一个现代化城市,市民们需要实时获取以下信息:
-
交通状态:道路是否畅通
-
天气预警:是否有暴风雨来临
-
紧急通知:城市安全状况
为了确保信息的实时性,城市设置了一个 "魔法公告板":
-
实时更新:一旦有新信息,立即显示在公告板上
-
强制刷新:所有市民查看公告板时,必须读取最新内容
-
禁止缓存:不允许市民私下记录公告板内容
这个魔法公告板就是 Java 中的volatile关键字,它确保变量的可见性。
📢 为什么需要 volatile?
1. 内存可见性问题
java
class TrafficSignal {
private boolean greenLight = false; // 交通信号灯
// 交警控制信号灯
public void setGreenLight() {
greenLight = true; // 变绿灯
System.out.println("交警:已切换为绿灯");
}
// 司机观察信号灯
public void observeLight() {
while (!greenLight) {
// 等待绿灯
}
System.out.println("司机:看到绿灯,开始行驶");
}
}
问题:
- 如果
greenLight没有被声明为volatile - 司机线程可能会缓存
greenLight的值 - 即使交警已经将灯变绿,司机线程也可能看不到变化
- 导致司机永远等待
🚥 volatile 的作用
1. 保证可见性
java
class TrafficSignal {
private volatile boolean greenLight = false; // 声明为volatile
// 交警控制信号灯
public void setGreenLight() {
greenLight = true; // 变绿灯
System.out.println("交警:已切换为绿灯");
}
// 司机观察信号灯
public void observeLight() {
while (!greenLight) {
// 等待绿灯
// 由于greenLight是volatile,每次循环都会读取最新值
}
System.out.println("司机:看到绿灯,开始行驶");
}
}
2. 禁止指令重排序
java
class Singleton {
private static volatile Singleton instance; // 声明为volatile
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) { // 第一次检查
synchronized (Singleton.class) {
if (instance == null) { // 第二次检查
// 这一步实际包含三个操作:
// 1. 分配内存
// 2. 初始化对象
// 3. 将引用指向内存地址
// volatile禁止这三个操作重排序
instance = new Singleton();
}
}
}
return instance;
}
}
🧠 底层实现原理
1. 内存屏障(Memory Barrier)
java
// 简化的volatile写操作
public void setVolatileVar(int value) {
this.volatileVar = value; // 普通写操作
// 插入StoreStore屏障
// 确保所有前面的普通写操作都完成
// 插入StoreLoad屏障
// 确保所有写操作对其他线程可见
}
// 简化的volatile读操作
public int getVolatileVar() {
// 插入LoadLoad屏障
// 确保所有后面的读操作都能读到最新值
// 插入LoadStore屏障
// 确保所有读操作都完成
return this.volatileVar; // 普通读操作
}
2. JMM(Java 内存模型)规则
- 对 volatile 变量写操作:会在写操作后插入一个 store 屏障,将本地内存中的共享变量刷新到主内存
- 对 volatile 变量读操作:会在读操作前插入一个 load 屏障,从主内存中读取共享变量
3. 硬件实现
- x86 架构:使用
LOCK前缀指令保证可见性 - ARM 架构:使用
DMB(数据内存屏障)指令 - Java:通过 JVM 将
volatile转换为对应平台的内存屏障指令
🚀 完整示例:城市公告系统
java
import java.util.concurrent.TimeUnit;
public class CityAnnouncementSystem {
public static void main(String[] args) {
AnnouncementBoard board = new AnnouncementBoard();
// 市长发布通知线程
Thread mayorThread = new Thread(() -> {
try {
System.out.println("市长:准备发布通知...");
TimeUnit.SECONDS.sleep(2); // 准备通知
board.setAnnouncement("紧急通知:今晚有暴风雨,请市民注意安全");
System.out.println("市长:通知已发布");
TimeUnit.SECONDS.sleep(5); // 5秒后更新通知
board.setAnnouncement("更新通知:暴风雨已减弱,解除警报");
System.out.println("市长:更新通知已发布");
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "市长");
// 市民查看通知线程
Thread citizen1 = new Thread(() -> board.watchAnnouncement("市民A"), "市民A");
Thread citizen2 = new Thread(() -> board.watchAnnouncement("市民B"), "市民B");
// 启动所有线程
System.out.println("城市公告系统启动...");
mayorThread.start();
citizen1.start();
citizen2.start();
// 等待市长线程完成
try {
mayorThread.join();
// 让市民有时间看到最后一条通知
TimeUnit.SECONDS.sleep(2);
citizen1.interrupt();
citizen2.interrupt();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("城市公告系统关闭...");
}
}
class AnnouncementBoard {
private volatile String announcement; // 声明为volatile
// 发布通知
public void setAnnouncement(String message) {
this.announcement = message;
}
// 查看通知
public void watchAnnouncement(String citizenName) {
String lastMessage = null;
while (!Thread.currentThread().isInterrupted()) {
String current = announcement; // 读取volatile变量
// 如果通知有更新
if (current != null && !current.equals(lastMessage)) {
lastMessage = current;
System.out.println(citizenName + " 看到通知:" + current);
}
}
System.out.println(citizenName + " 停止查看通知");
}
}
运行结果示例
plaintext
城市公告系统启动...
市长:准备发布通知...
市长:通知已发布
市民A 看到通知:紧急通知:今晚有暴风雨,请市民注意安全
市民B 看到通知:紧急通知:今晚有暴风雨,请市民注意安全
市长:更新通知已发布
市民A 看到通知:更新通知:暴风雨已减弱,解除警报
市民B 看到通知:更新通知:暴风雨已减弱,解除警报
城市公告系统关闭...
市民A 停止查看通知
市民B 停止查看通知
🌟 volatile 关键特性总结
-
保证可见性:
- 对 volatile 变量的写操作会立即刷新到主内存
- 对 volatile 变量的读操作会从主内存读取最新值
-
禁止指令重排序:
- 通过内存屏障保证指令执行顺序
- 适用于单例模式的双重检查锁定(DCL)
-
不保证原子性:
volatile不能替代synchronized- 对复合操作(如 i++)仍需要同步机制
-
适用场景:
-
状态标志(如
shutdownRequested) -
单例模式的双重检查锁定
-
替代复杂的同步方案(如只需要可见性)
-
通过这个城市公告系统的故事,我们理解了volatile的核心原理:它就像城市中的魔法公告板,确保所有市民(线程)都能看到最新的公告内容。在实际开发中,volatile是一种轻量级的同步机制,适用于只需要保证可见性的场景,但不能替代synchronized等重量级同步机制。