侧边栏壁纸
博主头像
colo

欲买桂花同载酒

  • 累计撰写 1823 篇文章
  • 累计收到 0 条评论

深入理解Java内存模型(JMM)与并发编程中的可见性问题

2025-12-12 / 0 评论 / 4 阅读

题目

深入理解Java内存模型(JMM)与并发编程中的可见性问题

信息

  • 类型:问答
  • 难度:⭐⭐⭐

考点

Java内存模型,volatile关键字,并发编程,可见性,重排序

快速回答

当多个线程访问共享变量时,volatile关键字通过以下机制保证可见性:

  • 禁止指令重排序(通过内存屏障)
  • 强制线程每次读取都从主内存获取最新值
  • 确保写入操作立即刷新到主内存

但需注意:volatile不保证原子性,复合操作仍需同步机制。

解析

问题场景

考虑以下代码在多线程环境下的行为:

public class VisibilityIssue {
    // 共享变量
    boolean ready = false;
    int result = 0;

    public void writer() {
        result = 42;   // 操作1
        ready = true;  // 操作2
    }

    public void reader() {
        if (ready) {   // 操作3
            System.out.println(result);  // 操作4
        }
    }
}

当线程A执行writer(),线程B执行reader()时,可能输出0而非42。

核心原理

Java内存模型(JMM)规范定义了线程如何与主内存交互:

  • 工作内存:每个线程有自己的工作内存(CPU缓存/寄存器副本)
  • 可见性问题:操作2可能先于操作1执行(重排序),且修改可能未及时同步到主内存
  • happens-before原则:JMM规定的内存可见性保证规则

解决方案:volatile关键字

volatile boolean ready = false;  // 添加volatile修饰

volatile通过以下机制工作:

  • 内存屏障
    • 写屏障:确保volatile写之前的操作不会被重排序到写之后
    • 读屏障:确保volatile读之后的操作不会被重排序到读之前
  • 即时可见:写操作直接刷新到主内存,读操作直接从主内存读取

代码验证

public class VolatileDemo {
    volatile boolean flag = false;

    public static void main(String[] args) throws InterruptedException {
        VolatileDemo demo = new VolatileDemo();

        // 线程1:修改flag
        new Thread(() -> {
            try {
                Thread.sleep(100);
                demo.flag = true;
                System.out.println("Flag set to true");
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }).start();

        // 线程2:检测flag变化
        new Thread(() -> {
            while (!demo.flag) {
                // 空循环等待
            }
            System.out.println("Flag detected as true");
        }).start();
    }
}

最佳实践

  • 适用场景:状态标志、一次性安全发布(如双重检查锁)
  • 限制:
    • 不保证复合操作(如i++)的原子性
    • 不能替代synchronized(当需要互斥访问时)
  • 替代方案:AtomicXXX类、显式锁、synchronized

常见错误

  • 误用volatile替代同步(如对多个变量做原子更新)
  • 忽视64位变量的非原子操作(long/double在32位JVM的非volatile声明)
  • 过度使用导致性能下降(volatile读比普通变量慢)

扩展知识

  • 内存屏障类型:LoadLoad, StoreStore, LoadStore, StoreLoad
  • final字段可见性:正确构造的对象中,final字段初始化对所有线程可见
  • happens-before规则
    • 程序顺序规则
    • volatile变量规则
    • 传递性规则