想象一个家庭有一个共用的留言板:
- 没有 volatile 的情况(可能出现可见性问题):
public class FamilyBoard {
private boolean hasNewMessage = false; // 没有volatile修饰
// 父母写留言
public void leaveMessage() {
hasNewMessage = true; // 孩子可能看不到这个更新
System.out.println("留言:记得买菜");
}
// 孩子查看留言
public void checkMessage() {
// 可能永远看不到新留言,因为使用的是自己CPU缓存中的旧值
if (hasNewMessage) {
System.out.println("收到新留言");
}
}
}
- 使用 volatile 确保可见性:
public class FamilyBoard {
private volatile boolean hasNewMessage = false; // 使用volatile修饰
// 父母写留言
public void leaveMessage() {
hasNewMessage = true; // 修改会立即被所有人看到
System.out.println("留言:记得买菜");
}
// 孩子查看留言
public void checkMessage() {
// 一定能看到最新的留言状态
if (hasNewMessage) {
System.out.println("收到新留言");
}
}
}
- volatile 不保证原子性的例子:
public class MessageCounter {
private volatile int messageCount = 0; // 即使用volatile修饰也不保证原子性
public void addMessage() {
messageCount++; // 这是复合操作:读取、加一、写入,不是原子的
}
// 需要用synchronized或AtomicInteger来确保原子性
public synchronized void addMessageSafely() {
messageCount++;
}
}
- volatile 禁止指令重排的例子:
public class MessageSystem {
private volatile boolean initialized = false;
private String message;
public void init() {
message = "重要通知"; // 1
initialized = true; // 2
}
public void readMessage() {
if (initialized) { // 3
System.out.println(message); // 4
}
}
}
在这个例子中:
- 没有 volatile:1和2的顺序可能被重排,导致其他线程看到 initialized 为 true 但 message 还未初始化
- 使用 volatile:保证 2 一定在 1 之后执行,其他线程看到 initialized 为 true 时,message 一定已经被初始化
实际应用场景:
- 状态标志:
public class TaskManager {
private volatile boolean shutdownRequested = false;
public void shutdown() {
shutdownRequested = true;
}
public void doWork() {
while (!shutdownRequested) {
// 执行任务
}
}
}
- 单例模式的双重检查锁定:
public class Singleton {
private static volatile Singleton instance; // volatile确保可见性和禁止重排
public static Singleton getInstance() {
if (instance == null) { // 第一次检查
synchronized (Singleton.class) {
if (instance == null) { // 第二次检查
instance = new Singleton();
}
}
}
return instance;
}
}
使用建议:
-
使用 volatile 当:
- 变量被多个线程共享
- 变量的新值不依赖旧值
- 只需要保证可见性,不需要保证原子性
-
不要使用 volatile 当:
- 需要保证操作的原子性(使用 synchronized 或 atomic 类)
- 变量值的更新依赖于当前值
通过这些例子,能更好地理解 volatile 的作用,它就像是在变量上贴了一个"立即生效"的标签,确保所有线程都能看到最新的值,但不能保证复杂操作的原子性。