1. 先简单解释 JMM 和变量访问路径

“在 Java 中,变量的访问遵循 Java 内存模型(JMM)。线程不会直接操作主内存中的变量,而是先将变量 load 到自己的工作内存中操作,最后 store 回主内存。”

Java内存模型示意图


2. 区分单线程 vs 多线程情境

📍 单线程下:

“在单线程环境中, load store 操作一般不会带来可见性问题。变量的值可能会直接缓存在寄存器或 CPU cache 中,编译器和 JVM 会进行优化,线程读写的数据是可见且一致的。”

📍 多线程下:

“在多线程环境下,每个线程都有自己的工作内存,可能会缓存变量副本。一个线程对变量的修改,如果没有及时 store 回主内存,其他线程的 load 操作可能读取不到最新值,导致可见性问题。”


Volatile

当一个变量被声明为 volatile 时,它会保证对这个变量的写操作会立即刷新到主存中,而对这个变量的读操作会直接从主存中读取,从而确保了多线程环境下对该变量访问的可见性。这意味着一个线程修改了 volatile 变量的值,其他线程能够立刻看到这个修改,不会受到各自线程工作内存的影响。

在 Java 中,volatile 关键字除了可以保证变量的可见性,还有一个重要的作用就是防止 JVM 的指令重排序。如果将变量声明为 volatile,在对变量进行读写操作时,会通过插入特定的内存屏障来禁止指令重排序。


不保证原子性

package com.yeyu;

public class VolatileCounter {
    private static volatile int count = 0;

    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 5000; i++) {
                count++;
            }
        });

        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 5000; i++) {
                count++;
            }
        });

        t1.start();
        t2.start();
        try {
            t1.join();
            t2.join();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }

        System.out.println("现在count的值是: " + count);
    }
}