题目
分析并解决多线程环境下的内存可见性问题
信息
- 类型:问答
- 难度:⭐⭐
考点
内存可见性,volatile关键字,JMM happens-before原则
快速回答
当多个线程访问共享变量时,由于Java内存模型的工作机制,可能出现内存可见性问题:
- 核心问题:线程A修改的变量值对线程B不可见
- 根本原因:CPU缓存与主内存不一致,缺少happens-before保证
- 解决方案:
volatile关键字或同步机制 - 修复代码:
private volatile boolean flag = true;
问题场景分析
给定以下代码:
public class VisibilityDemo {
private boolean flag = true;
public void writer() {
flag = false; // 线程A执行
}
public void reader() {
while (flag) { // 线程B执行
// 空循环
}
System.out.println("Flag changed to false");
}
}当线程A调用writer()而线程B调用reader()时,可能出现:
线程B永远无法退出循环,即使线程A已将flag设为false。
原理说明
Java内存模型(JMM)规定:
- 每个线程有自己的工作内存(CPU缓存抽象)
- 共享变量存储在主内存中
- 默认情况下,线程修改变量后不立即刷新主内存
- 缺少happens-before关系时,其他线程可能读取过期值

(示意图:线程B无法看到线程A的修改)
解决方案与代码示例
方案1:使用volatile关键字
private volatile boolean flag = true;作用机制:
- 保证变量的修改立即可见到其他线程
- 禁止指令重排序(内存屏障)
- 建立happens-before关系:写操作先于后续读操作
方案2:使用synchronized同步
public synchronized void writer() {
flag = false;
}
public synchronized void reader() {
while (flag) { ... }
}双重保证:
- 互斥执行确保原子性
- 同步块退出时自动刷新工作内存到主内存
最佳实践
- volatile适用场景:
- 状态标志(如示例中的flag)
- 一次性安全发布(double-checked locking)
- 读多写少的场景
- synchronized适用场景:
- 需要保证原子性+可见性(如计数器)
- 复合操作(check-then-act)
- 避免误区:
- volatile不能替代synchronized(不保证原子性)
- final变量在构造函数中初始化具有可见性保证
常见错误
- 错误1:认为变量修改自然具有可见性
// 错误!仍可能出现可见性问题 boolean running = true; - 错误2:误用volatile保证原子操作
volatile int count = 0; count++; // 非原子操作! - 错误3:过度依赖线程休眠
Thread.sleep(100); // 不能替代正确的同步机制
扩展知识
- happens-before规则:
- 程序顺序规则
- volatile变量规则
- 传递性规则
- 内存屏障类型:
- LoadLoad屏障
- StoreStore屏障
- LoadStore屏障
- StoreLoad屏障(volatile写插入)
- 替代方案:
- AtomicBoolean(适合需要原子性的场景)
- 显式锁(ReentrantLock)