volatile

mac2026-05-17  6

文章目录

简介可见性重排序数据依赖性重排序对多线程的影响

简介

java内存模型规定,volatile变量的每次修改都必须立即回写到主内存中,volatile变量的每次使用都必须从主内存刷新最新的值。

可见性

volatile可见性示例

public class VolatileTest { // public static int finished = 0; public static volatile int finished = 0; private static void checkFinished() { while (finished == 0) { // 不断检测 finished 的值,当finished=1时退出循坏 } System.out.println("finished"); } private static void finish() { finished = 1; } public static void main(String[] args) throws InterruptedException { // 起一个线程检测是否结束 new Thread(() -> checkFinished()).start(); // 主线程休眠1秒钟,让上面的线程先启动 Thread.sleep(1000); // 主线程将finished标志置为1 finish(); System.out.println("main finished"); } }

上面的代码中,finished初始值为0。首先,新建一个线程,该线程使用while死循环不断检测finished的值,直到finished不等于0时才退出while循坏。随后主线程将finished的值更新为1。运行上面的代码可以发现,使用volatile修饰finished变量时程序可以正常结束,不使用volatile修饰时这个程序永远不会结束。

因为不使用volatile修饰时,checkFinished()所在的线程每次都是读取它自己工作内存中的变量的值,这个值一直为0,所以一直都不会跳出while循环。

使用volatile修饰时,checkFinished()所在的线程每次都是从主内存中加载最新的值,当finished被主线程修改为1的时候,它会立即感知到,进而会跳出while循环。

除了volatile之外,还有两个关键字也可以保证可见性,它们是synchronized和final。

synchronized的可见性是由“对一个变量执行unlock操作之前,必须先把此变量同步回主内存中,即执行store和write操作”这条规则获取的。

final的可见性是指被final修饰的字段在构造器中一旦被初始化完成,那么其它线程中就能看见这个final字段了。

重排序

重排序是指编译器和处理器为了优化程序性能而对指令序列进行重新排序的一种手段。

数据依赖性

如果两个操作访问同一个变量,且这两个操作有一个写操作,此时这两个操作就存在数据依赖性,数据依赖性分为三种类型。

名称代码示例说明写后读a=1;b=a;写一个变量之后,再读这个位置写后写a=1;a=2;写一个变量之后,再写这个变量读后写a=b;b=1;读一个变量后,再写这个变量

上面三种情况,只要重排序两个操作的执行顺序,程序的执行结果就会被改变。

编译器和处理器在进行重排序时,会遵守数据的依赖性,编译器和处理器不会改变存在数据依赖性的两个操作的执行顺序。这里所说的数据依赖性仅仅针对单个处理器中执行的指令序列和单个线程中执行的操作,不同处理器之间和不同线程之间的数据依赖性不被编译器和处理器考虑。

重排序对多线程的影响

public class Test { int a = 0; boolean flag = false; public void write() { a = 1; // 1 flag = true; // 2 } public void read() { while (!flag){ // 3 } int i = a*a; // 4 } }

上面的代码,flag是个标记,用来标识a是否已经被写入。假设现在有两个线程A和B,A首先执行write()方法,随后B执行read()方法。线程B在执行操作4时,能否看到线程A在操作1对共享变量a的写入呢?

答案是:不一定。

由于操作1和操作2之间没有数据依赖关系,编译器和处理器可以对这两个操作进行重排序;同样,操作3和操作4之间也没有数据依赖关系,编译器和处理器也可以对这两个操作进行重排序。

当操作1和操作2发生重排序时:

时间线程A线程B1flag = true;2while (!flag)3int i = a*a;4a = 1;

程序执行时,线程A先执行flag = true;随后线程B执行while (!flag),由于条件判断为false,调出while循环,然后线程B读取a执行int i = a*a。而此时,变量a还没有被线程A写入,在这里多线程程序的语义被重排序破坏了。

最新回复(0)