在Java中内存可见性是什么意思_Java并发核心概念解析

内存可见性指线程对共享变量的修改能否被其他线程及时看到,因JMM中线程操作变量需经工作内存与主内存间复制,无同步时修改可能不及时刷回主内存;volatile通过强制读写主内存及禁止重排序解决该问题,但不保证原子性;synchronized也保证可见性,但以加锁和更大开销为代价,提供原子性保障。

内存可见性,指的是一个线程对共享变量的修改,能被其他线程“及时看到”。它不是“会不会被看到”,而是“什么时候被看到”——不加同步时,flag = true 执行完,另一个线程仍可能永远读到 false

为什么改了值,另一个线程却读不到?

Java 内存模型(JMM)把内存分成两层:主内存(所有线程共享)和每个线程私有的工作内存。线程操作变量时,并不直接读写主内存,而是:

  • 从主内存把变量副本加载进自己的工作内存
  • 在工作内存中读/写
  • 再择机把修改刷回主内存(时机不确定!)

问题就出在第三步:没有同步机制时,JIT 编译器或 CPU 缓存可能让线程一直用自己工作内存里的旧值,根本不查主内存。比如下面这段代码会死循环:

public class VisibilityDemo {
    static boolean run = true;

    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() -> {
            while (run) { } // 空循

环 System.out.println("退出"); }); t.start(); Thread.sleep(100); run = false; // 主线程改了,但 t 线程看不到 } }

volatile 是怎么解决可见性的?

volatile 不是锁,但它强制两点:

  • volatile 变量时,必须立刻刷新到主内存
  • volatile 变量时,必须从主内存重新加载,禁止使用工作内存缓存
  • 同时禁止编译器和处理器对该变量的读写做重排序(保证有序性)

只需把上面的 static boolean run = true 改成 static volatile boolean run = true,问题就消失。但注意:volatile 只保可见性和有序性,不保原子性——count++ 这种复合操作仍需 synchronizedAtomicInteger

synchronized 也能解决可见性,但它和 volatile 有什么区别?

是的,synchronized 同样具备可见性语义:

  • 解锁前,自动把工作内存中所有共享变量刷新到主内存
  • 加锁时,强制清空工作内存中对应变量的副本,下次读必须从主内存取

区别在于粒度和开销:

  • volatile 是轻量级、无锁、只作用于单个变量
  • synchronized 是重量级、有锁、作用于代码块或方法,附带互斥(原子性)保障
  • 如果只是“通知状态变更”(如开关标志),优先用 volatile;如果涉及“读-改-写”逻辑(如计数、状态流转),必须用 synchronized 或原子类

最容易被忽略的一点:可见性问题往往在高并发下不暴露,而是在低负载、长时间运行或 JIT 优化后才突然复现——所以不能靠“测试没出错”来判断是否安全。