1. 先简单解释 JMM 和变量访问路径
“在 Java 中,变量的访问遵循 Java 内存模型(JMM)。线程不会直接操作主内存中的变量,而是先将变量 load
到自己的工作内存中操作,最后 store
回主内存。”
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);
}
}