当一个线程对volatile
变量进行写操作时:
-
JMM会确保该写操作之前的所有操作(包括普通变量的读写)都已完成,并且这些操作的结果对后续的
volatile
读操作是可见的。 -
这意味着,
volatile
写操作之前的操作不会被重排序到写操作之后。
示例:
java
复制
int x = 1; volatile boolean flag = false; // 线程1 x = 2; // 普通写操作 flag = true; // volatile 写操作
在以上代码中:
-
x = 2
的写操作一定会在flag = true
之前完成。 -
当
flag = true
写入主内存后,其他线程可以看到x = 2
的结果。
2. volatile
读操作的happens-before
规则
当一个线程对volatile
变量进行读操作时:
-
JMM会确保该读操作之后的所有操作(包括普通变量的读写)都不会被重排序到读操作之前。
-
这意味着,
volatile
读操作之后的操作可以看到volatile
变量的最新值。
示例:
java
复制
// 线程2 if (flag) { // volatile 读操作 System.out.println(x); // 普通读操作 }
在以上代码中:
-
flag
的读操作会从主内存中加载最新值。 -
System.out.println(x)
的读操作一定会在flag
的读操作之后执行,并且可以看到x
的最新值。
3. volatile
的happens-before
关系
volatile
变量的读写操作建立了线程之间的happens-before
关系:
-
写操作
happens-before
读操作:如果一个线程写入了volatile
变量,另一个线程读取了该变量,那么写操作之前的所有操作对读操作之后的代码是可见的。
示例:
java
复制
// 线程1 x = 42; // 普通写操作 flag = true; // volatile 写操作 // 线程2 if (flag) { // volatile 读操作 System.out.println(x); // 普通读操作 }
在以上代码中:
-
线程1的
x = 42
和flag = true
之间存在happens-before
关系。 -
线程2的
flag
读操作和System.out.println(x)
之间也存在happens-before
关系。 -
因此,线程2可以确保看到
x = 42
的结果。
4. volatile
与指令重排序
volatile
不仅保证了可见性,还通过禁止指令重排序来确保程序的有序性:
-
写操作:
volatile
写操作之前的操作不会被重排序到写操作之后。 -
读操作:
volatile
读操作之后的操作不会被重排序到读操作之前。
5. 总结
-
写操作:
volatile
写操作会确保之前的所有操作都已完成,并将结果刷新到主内存。 -
读操作:
volatile
读操作会从主内存加载最新值,并确保之后的操作不会被重排序到读操作之前。 -
happens-before
关系:volatile
变量的读写操作建立了线程之间的happens-before
关系,确保了多线程环境下的可见性和有序性。