1. 什么是 volatile 关键字?
volatile
是 Java 中的一个关键字,用于修饰变量,确保变量的可见性和禁止某些编译器和处理器的优化。volatile
变量的主要作用是确保多线程环境下的变量可见性和有序性。
- 可见性:当一个线程修改了
volatile
变量的值,其他线程可以立即看到这个变化。 - 有序性:
volatile
变量的写操作不会被重排序到其前面的读操作之前,读操作也不会被重排序到其后面的写操作之后。
2. volatile 关键字底层原理
volatile
关键字的底层原理主要涉及到内存模型和硬件指令。
内存模型:
Java 内存模型(Java Memory Model, JMM)定义了多线程环境下的内存可见性和有序性规则。volatile
变量的读写操作会直接与主内存进行交互,而不是线程的工作内存。
-
写操作:
- 当一个线程写入一个
volatile
变量时,JMM 会确保在此之前对该变量的所有读写操作都已完成,并且结果已经刷新到主内存。 - 写操作完成后,其他线程可以看到最新的值。
- 当一个线程写入一个
-
读操作:
- 当一个线程读取一个
volatile
变量时,JMM 会确保在此之后对该变量的所有读写操作都不会被重排序到此读操作之前。 - 读操作会从主内存中读取最新的值,而不是线程的工作内存。
- 当一个线程读取一个
硬件指令:
volatile
变量的读写操作通常会生成特定的内存屏障(Memory Barrier)指令,确保内存操作的顺序性。
- LoadStore 屏障:在读取
volatile
变量之前插入,确保之前的读操作不会被重排序到此读操作之后。 - StoreStore 屏障:在写入
volatile
变量之后插入,确保之后的写操作不会被重排序到此写操作之前。 - StoreLoad 屏障:在写入
volatile
变量之后插入,确保之后的读操作不会被重排序到此写操作之前。
3. volatile 的 happens-before 关系
volatile
变量的读写操作建立了 happens-before 关系,确保了内存可见性和有序性。
-
写操作 happens-before 读操作:
- 如果一个线程 A 写入一个
volatile
变量 V,然后另一个线程 B 读取同一个volatile
变量 V,那么线程 A 的写操作 happens-before 线程 B 的读操作。 - 这意味着线程 A 对 V 的写操作对线程 B 可见,线程 B 读取到的是最新的值。
- 如果一个线程 A 写入一个
-
传递性:
- 如果 A happens-before B,B happens-before C,那么 A happens-before C。
- 例如,线程 A 写入
volatile
变量 V,然后写入普通变量 X,线程 B 读取volatile
变量 V,然后读取普通变量 X,那么线程 A 对 X 的写操作对线程 B 可见。
4. volatile 禁止重排序
volatile
变量的读写操作禁止了某些编译器和处理器的优化,确保了内存操作的顺序性。
-
禁止写重排序:
volatile
变量的写操作不会被重排序到其前面的读操作之前。- 例如,
a = 1; b = 2; volatileVar = true;
中,a = 1
和b = 2
不会被重排序到volatileVar = true
之后。
-
禁止读重排序:
volatile
变量的读操作不会被重排序到其后面的写操作之后。- 例如,
volatileVar = true; a = 1; b = 2;
中,a = 1
和b = 2
不会被重排序到volatileVar = true
之前。
以下是一个使用 volatile
关键字的示例,展示了 volatile
变量的可见性和禁止重排序的特性:
public class VolatileExample {
private static volatile boolean flag = false;
public static void main(String[] args) throws InterruptedException {
Thread writer = new Thread(() -> {
System.out.println("Writer thread started.");
// 模拟一些耗时操作
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
flag = true;
System.out.println("Writer thread set flag to true.");
});
Thread reader = new Thread(() -> {
System.out.println("Reader thread started.");
while (!flag) {
// 等待 flag 变为 true
}
System.out.println("Reader thread detected flag as true.");
});
writer.start();
reader.start();
writer.join();
reader.join();
}
}
在这个示例中,writer
线程在一段时间后将 flag
设置为 true
,reader
线程会不断检查 flag
的值,直到 flag
变为 true
。由于 flag
是 volatile
变量,reader
线程可以看到 writer
线程对 flag
的最新修改。
volatile
关键字在 Java 中用于确保变量的可见性和禁止某些编译器和处理器的优化。通过建立 happens-before 关系,volatile
变量的读写操作确保了内存可见性和有序性。正确使用 volatile
可以有效解决多线程环境下的数据竞争问题。