volatile变量一般是比较难以理解,这里坐着希望列举几个小例子让大家充分理解它的使用方法。
对于定义了volatile的变量,这里有两条特性需要大家记住:
第一、保证此变量对所有的线程都是可见性,这里的可见行是指一个线程修改了这个变量的值,新值对于其他的线程来说是可知的。这里,volatile变量不存在一致性问题,volatile变量在并发情况下并非安全。这通过一段程序说明:
package cn.edu.hust.jvm; public class MainTest { public static volatile int value=0; public static void incr() { value++; } public static void main(String[] args) { Thread[] threads=new Thread[10]; for (int i=0;i<10;i++) { threads[i]=new Thread(new Runnable() { @Override public void run() { for (int j=0;j<1000;j++) { incr(); } } }); threads[i].start(); } System.out.println(value); } }
如果volatile变量时并发安全的话,那么输出结果应该是10000,这里的实际输出结果是
可以看出volatile变量是线程不安全的。那么这是为什么呢?
这里主要的原因是value++这一行代码中,执行这一行代码前,每一个线程的工作空间需要读取value的值大小,这一时刻每一个线程的value值都是一样的的,但是在执行自增操作的时候,可能有些线程执行比较快,有些执行的比较慢,当慢的执行操作时,可能value的值改变。
所以我们在使用volatile关键值时,也需要使用synchronized关键值活着并发包下的类来保证原子操作。
第二、禁止指令重排序优化。这里的重排序,指的是在正确结果的前提下,或者是保证依赖关系的前提下,cpu采用多条指令不按照程序顺序执行分发给各个电路单元处理。
对于上一节的8种规则,volatile关键字有如下的规则:
有load操作才有use工作,反过来也成立,这里必须连续一起出现,这保证类线程从主内存获取最新值,也就是volatile的可见行
有assign动作才有store动作,反过来也成立,这里必须连续一起出现。
还有最后一条是动作执行的先后顺序。这保证了禁止指令重新排序问题。