volatile变量的可见性

mac2025-09-25  22

可见性

volatile可以保证此变量对所有线程立即可见。这里所说的’可见性‘是指当一条线程对变量进行修改后,新值对其他线程是立即可见的。但是普通变量做不到这一点。首先,变量值之间的传递是通过主内存进行传递的。如:当线程A对一个普通变量进行修改后,需要把修改后的变量值向主内存回写,另外一条线程B只有当线程A回写到主内存之后,新的变量值才会对线程B可见,但是在多线程并发的情况下,往往线程B读取的值不是线程A改掉的值。 对于volatile变量对所有线程是立即可见。换句话说,volitale变量在各个线程之间是保持一致的,所以基于volitale变量的运算是线程安全的。 这往往存在一个误区,我们可以说volitale变量在各个线程之间是保持一致的,但是不能得出volitale变量参与运算是线程安全的。请看下面的代码。

private static volatile int race=0; public static void increase(){ race++; } private static int NUMBER=20; public static void main(String[] args) { Thread[] th=new Thread[NUMBER]; for (Thread thread : th) { thread=new Thread(new Runnable(){ public void run() { for(int i=0;i<=10000;i++){ increase(); } } }); thread.start(); } System.err.println(Thread.activeCount()); while(Thread.activeCount()>1) Thread.yield(); System.err.println(race); }

这段代码发起了20条线程,每条线程执行对race执行10000次递增操作,在代码能正确的并发的前提下,执行结果应该为200000,但是当你多次运行之后,得出的结果往往比预期值小很多。这个什么原因导致的。 我们利用javap反编译这段代码,发现increase方法在class文件中是由四条字节码指令,return 可以不算。当执行getstatic命令把race的值取到操作栈时,volitale保证了race的正确性,但当执行icons_1、idd的命令式,其他线程可能已经把已经把race的值增大了,而在操作栈顶的数据就变成了过期数据,所以在执行putstatic命令后就可能把较小的数据回写的主内存中。

public static void increase(); code: Stack=2,Locals=0,Args_size=0 0: getstatic 把race的值取到操作栈顶时,volitale保证了race的正确性 3: iconst_1 4: idd 5: putstatic 8: reutrn LineNumberTable: line 14: 0 line 15: 8

即使编译出只有一条字节码指令,也不能保证执行这条这令是原子操作,一条字节码指令在解析的时候也可能转换成若干条本地机器代码指令。

结论

1.volitale只能保证变量的可见性,不能保证操作的原子性。我们仍然需要通过 加锁来保证操作的原子性。

应用场景

1.运算结果并不依赖变量的当前值,或者能够确保只有单一线程修改的值。 2.变量不需要与其他状态的变量共同参与约束。 下面的代码就很适合用volitale变量进行并发控制,当执行 shutdown()的方法时,能保证所有线程执行的dowork()方法停下来

volatile boolean checkFlag=false; public void shutdown(){ checkFlag=true; } public void dowork(){ if(!checkFlag){ } }
最新回复(0)