保证此变量对所有线程的立即可见性
? 当变量的值被修改之后,新值对于其他线程是立即可知的。普通变量并不能做到这一点,因为普通变量的值在线程之间的传递是要进过主内存来完成的。比如当线程A对变量进行了回写操作,线程B只有在A回写完成之后,在对主内存操作,新值才对B是可见的。在A回写到主内存的过程中,B读取的依旧是旧值。
? 但是这并不可以推导出基于volatile变量的运算在并发下是安全的
,因为在Java中的运算操作符并不是原子性
的。这导致了volatile变量在并发下运算是不安全
的。
通过代码验证volatile变量在并发下运算是不安全
首先我们创建20个线程,每个线程对volatile变量进行1000次的自增操作。
public class VolatileTest {
private static volatile int count = 0;
public static void increment(){
count++;
}
public static void main(String[] args) {
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "线程开始对count进行递增操作");
for (int i = 0; i < 1000; i++) {
increment();
}
System.out.println(Thread.currentThread().getName() + "线程对count递增操作结束");
}
};
for (int i = 0; i < 20; i++) {
Thread thread = new Thread(runnable);
thread.setName((i+1) + "号线程");
thread.start();
}
while (Thread.activeCount() > 2){
Thread.yield();
}
System.out.println("所有线程结束,count = " + count);
}
}
如果此程序在并发下是安全的,那么count的值最后肯定是20*1000 = 20000;也就是说,如果运行结果为20000,那么volatile变量在并发下运算是安全的
通过多次运行程序,我们发现,count的值永远比20000小。
那么,这是为什么呢?
我们将上方的代码进行反编译,然后分析increment方法的字节码指令。
0 getstatic #2 <cn/shaoxiongdu/chapter6/VolatileTest.count : I>
3 iconst_1
4 iadd
5 putstatic #2 <cn/shaoxiongdu/chapter6/VolatileTest.count : I>
8 return
我们可以发现,一行count++代码被分为 4行字节码文件去执行。通过对字节码的分析,我们发现,
当偏移量为0的字节码getStatic将count的值从局部变量表取到操作数栈顶的时候,volatile
保证了此时count的值是正确的,但是在执行iconst_1, iadd这些操作的时候,其他线程已经把count的值改变了,此时,操作数栈顶的count为过期的数据,所以putStatic字节码指令就有可能将较小的值同步到主内存中。因此最终的值会比20000稍微小。
也就是说,volatile变量在并发下运算是不安全的
。
在并发环境下,volatile的变量只是对全部线程即时可见的,如果要进行写的操作,还是要通过加锁来解决。