volatile关键字在Java编程中,应用的比较少,主要原因无外乎两点,一是因为Java1.5之前该关键字在不同的系统下的表现没有统一,所带来的问题就是程序的可移植性比较差,其二,就是因为非常难设计,而且误用比较多,所以导致volatile的名誉受损。
我们都知道,每个线程都运行在栈内存之中,每个线程都有自己的工作内存空间,比如说寄存器,高速缓冲储存器等,线程的计算一般是 通过在工作内存进行交互的,我们来看一下图片,来更明确这一点:
从图上,我们很清楚的知道了线程读入变量的时候是从主内存加载到工作内存中的值,线程的写也是在这一个工作内存中,之后在刷新到主内存中,这样就会产生一个问题,就是线程读取的值是不新鲜的值,会出现不同线程所持有的“相同”公共变量不同步的情况。
应对这种情况的办法还是很多的,比如说在更新和读取时使用synchronized的同步代码,或者使用Lock解决该问题,不过,Java可以使用非常简单的方法实现该问题,比如说,这篇文章的主角,volatile,加上该关键字之后,可以确保每一条线程在对改制进行操作的时候是面向主内存中的值,而不是工作内存的值,示意图:
这就是volatile的原理,但是,这是不是就能保证数据的同步性呢?
答案是,当然不能。
以下代码可以尝试运行一下:
public class Main { public volatile static int count = 0; /** * @param _something * @author mikecoder * @throws InterruptedException */ public static void main(String[] _something) throws InterruptedException{ class UnsafeThread implements Runnable{ @Override public void run() { for (int i = 0; i < 10000; i++) { Math.hypot(Math.pow(92456789, i), Math.cos(i)); } count ++; } public int getCount(){ return count; } } int value = 1000; int loops = 0; ThreadGroup threadGroup = Thread.currentThread().getThreadGroup(); while(loops++ < value){ UnsafeThread unsafeThread = new UnsafeThread(); for (int i = 0; i < 1000; i++) { new Thread(new UnsafeThread()).start(); } do { Thread.sleep(5); } while (threadGroup.activeCount() != 1); if (unsafeThread.getCount() != value) { System.out.println("Loops:" + loops + " Unsafe!"); System.out.println("Count:" + unsafeThread.getCount()); System.exit(0); } count = 0; System.out.println("Loops:" + loops + " Safe!"); } } }
恩,这就是一个线程不安全的程序,当然,出现不安全的时候需要一定的条件,比如说CPU负担过重,具体的线程调度就不扯了,基本上也就是单核和多核的区别.这是我的运行结果:
在第12次循环的时候出现了线程不安全的情况.所以,volatile并不能保证数据是同步的,只能保证线程得到的数据是最新的.
那么,我们应该在什么情况下使用volatile关键字呢?
其实很简单.只要符合以下两个条件就能使用volatile,并且能收到很不错的效果.
1.对变量的写入不依赖变量的当前值,或者只有一个线程更新变量的值.
2.该变量不会和其他状态变量一起被列为不变性条件中(注1).
那么,本文的这个问题该怎么解决呢?其实很简单,在修改变量的时候进行同步或者加锁就行.具体的实现方法就不说了,相信大家也都知道了.
恩,就这样.
注:
1.当满足一下条件时,对象才是不可变的:
·对象创建以后起状态就不能修改。
·对象的所有域都是final类型。
·对象是正确创建的(在对象的创建期间,this引用没有逸出)。
根本原因是因为count++不是原子操作