转载请注明出处: http://blog.youkuaiyun.com/ns_code/article/details/17101369
volatile用处说明
在JDK1.2之前,Java 的内存模型实现总是从主存(即共享内存)读取变量,是不需要进行特别的注意的。而随着JVM的成熟和优化,现在在多线程环境下volatile 关键字 的使用变得非常重要。
在当前的Java内存模型下,线程 可以把变量 保 存在本地内存(比如机器的寄存器)中,而不是直接在主存中进行读写。这就可能造成一个线程在主存中修改了一个变量 的值,而另外一个线程还继续使用它在寄存器中的变量值的拷贝,造成数据的不一致。
Java语言 规范中指出:为了获得最佳速度,允许线程保存共享成员变量 的私有拷贝,而且只当线程进入或者离开同步代码块 时才将私有拷贝与共享内存中的原始值进行比较。
这样当多个线程同时与某个对象交互时,就必须注意到要让线程及时的得到共享成员变量 的变化。而volatile 关键字 就是提示JVM:对于这个 成员变量 ,不能保存它的私有拷贝,而应直接与共享成员变量交互。
volatile是一种稍弱的同步机制,在访问volatile变量时不会执行加锁操作,也就不会执行线程阻塞,因此volatilei变量是一种比synchronized关键字更轻量级的同步机制。
使用建议:在两个或者更多的线程需要访问的成员变量 上使用volatile。当要访问的变量 已在synchronized代码块中,或者为常量 时,没必要使用volatile。
由于使用volatile屏蔽掉了JVM中必要的代码优化 ,所以在效率上比较低,因此一定在必要时才使用此关键字 。
示例程序
下面给出一段代码,通过其运行结果来说明使用关键字volatile产生的差异,但实际上遇到了意料之外的问题:
public class Volatile extends Object implements Runnable { private int value; private volatile boolean missedIt; private long creationTime; public Volatile() { value = 10 ; missedIt = false ; creationTime = System.currentTimeMillis(); } public void run() { print("entering run()" ); while ( value < 20 ) { if ( missedIt ) { int currValue = value; Object lock = new Object(); synchronized ( lock ) { } int valueAfterSync = value; print("in run() - see value=" + currValue + ", but rumor has it that it changed!" ); print("in run() - valueAfterSync=" + valueAfterSync); break ; } } print("leaving run()" ); } public void workMethod() throws InterruptedException { print("entering workMethod()" ); print("in workMethod() - about to sleep for 2 seconds" ); Thread.sleep(2000 ); value = 50 ; print("in workMethod() - just set value=" + value); print("in workMethod() - about to sleep for 5 seconds" ); Thread.sleep(5000 ); missedIt = true ; print("in workMethod() - just set missedIt=" + missedIt); print("in workMethod() - about to sleep for 3 seconds" ); Thread.sleep(3000 ); print("leaving workMethod()" ); } private void print(String msg) { long interval = System.currentTimeMillis() - creationTime; String tmpStr = " " + ( interval / 1000.0 ) + "000" ; int pos = tmpStr.indexOf( "." ); String secStr = tmpStr.substring(pos - 2 , pos + 4 ); String nameStr = " " + Thread.currentThread().getName(); nameStr = nameStr.substring(nameStr.length() - 8 , nameStr.length()); System.out.println(secStr + " " + nameStr + ": " + msg); } public static void main(String[] args) { try { Volatile vol = new Volatile(); Thread.sleep(100 ); Thread t = new Thread(vol); t.start(); Thread.sleep(100 ); vol.workMethod(); } catch ( InterruptedException x ) { System.err.println("one of the sleeps was interrupted" ); } } }
按照以上的理论来分析,由于value变量不是volatile的,因此它在main线程中的改变不会被Thread-0线程(在main线程中新开启的线程)马上看到,因此Thread-0线程中的while循环不会直接退出,它会继续判断missedIt的值,由于missedIt是volatile的,当main线程中改变了missedIt时,Thread-0线程会立即看到该变化,那么if语句中的代码便得到了执行的机会,由于此时Thread-0依然没有看到value值的变化,因此,currValue的值为10,继续向下执行,进入同步代码块,因为进入前后要将该线程内的变量值与共享内存中的原始值对比,进行校准,因此离开同步代码块后,Thread-0便会察觉到value的值变为了50,那么后面的valueAfterSync的值便为50,最后从break跳出循环,结束Thread-0线程。
意料之外的问题
但实际的执行结果如下:
从结果中可以看出,Thread-0线程并没有进入while循环,说明Thread-0线程在value的值发生变化后,missedIt的值发生变化前,便察觉到了value值的变化,从而退出了while循环。这与理论上的分析不符,我便尝试注释掉value值发生改变与missedIt值发生改变之间的线程休眠代码Thread.sleep(5000),以确保Thread-0线程在missedIt的值发生改变前,没有时间察觉到value值的变化。但执行的结果与上面大同小异(可能有一两行顺序不同,但依然不会打印出if语句中的输出信息)。
问题分析
在JDK1.7~JDK1.3之间的版本上输出结果与上面基本大同小异,只有在JDK1.2上才得到了预期的结果,即Thread-0线程中的while循环是从if语句中退出的,这说明Thread-0线程没有及时察觉到value值的变化。
这里需要注意:volatile是针对JIT带来的优化,因此JDK1.2以前的版本基本不用考虑,另外,在JDK1.3.1开始,开始运用HotSpot虚拟机,用来代替JIT。因此,是不是HotSpot的问题呢?这里需要再补充一点:
JIT或HotSpot编译器在server模式和client模式编译不同,server模式为了使线程运行更快,如果其中一个线程更改了变量boolean flag 的值,那么另外一个线程会看不到,因为另外一个线程为了使得运行更快所以从寄存器或者本地cache中取值,而不是从内存中取值,那么使用volatile后,就告诉不论是什么线程,被volatile修饰的变量都要从内存中取值。《内存栅栏》
但看了这个帖子 http://segmentfault.com/q/1010000000147713 (也有人遇到同样的问题了)说,尝试了HotSpot的server和client两种模式,以及JDK1.3的classic,都没有效果,只有JDK1.2才能得到预期的结果。
哎!看来自己知识还是比较匮乏,看了下网友给出的答案,对于非volatile修饰的变量,尽管jvm的优化,会导致变量的可见性问题,但这种可见性的问题也只是在短时间内高并发的情况下发生,CPU执行时会很快刷新Cache,一般的情况下很难出现,而且出现这种问题是不可预测的,与jvm, 机器配置环境等都有关。
姑且先这么理解吧!一点点积累。。。
正确的分析在这里:http://blog.youkuaiyun.com/ns_code/article/details/17382679
这里附上分析结果时参考的帖子及文章
http://segmentfault.com/q/1010000000147713
http://www.iteye.com/problems/98213
http://www.oldcaptain.cc/articles/2013/08/21/1377092100971.html