关键字volatile是虚拟机提供的一种轻量级的同步机制,但在做多线程开发时,大部分的人都不太习惯喜欢用volatile,而是直接用关键字synchronized来进行同步。但有些时候在解决线程安全问题时,使用volatile关键字要比使用synchronized同步函数更好一些。下面我们详细分析一下在Java中关键字volatile到底是一个什么样的东东,底层是怎么实现的。
在JMM中虚拟机对volatile关键字专门定义了一些特殊的访问规则,来实现线程的轻量级同步。当变量是一个volatile变量时,它就会具有两种特殊的特性。
按照代码的逻辑,每个线程都会调用1000次increase()方法,也就是每一个线程都会执行1000次volatile变量的自增。并且我们开启了10个线程,按照volatile变量可见性的分析,在正常情况下最后输出的值就应该是10000。但实际的结果却小于10000这是为什么呢?
问题的原因就是虽然volatile变量具有可见性,能够保证其它线程立即获取到更新后的值。但问题的原因却是自增运算导致的。也就是race++。在Java中自增运算并不是原子性的操作,所以在程序运行自增运算时,其它线程也有可能执行。我们使用javap命令来查看一下编译后的字节码就明白了其中的缘由了。
我们主要看increase()方法中的字节码即可。在字节码中使我们知道虽然在源码中我们只写了一条语句,但是在字节码执行时,是需要5条指令来实现自增运算的。虽然volatile变量可以保证在虚拟机执行字节码时变量是最新的值,但是并不能保证在执行其它指令时,别的线程不在更新volatile变量。如果在此线程没有执行自增运算指令时,其它线程已经对volatile变量进行了修改,那么在此线程在执行自增运算时,得到的volatile变量已经是过期的了,既然是过期的值,那就一定会比真正的值要小,于是此线程就把已经过期的volatile变量的值同步到了主内存中,所以程序在执行时,最后的执行结果会比预计执行的值要小。
使用volatile变量的别一种特性就是禁止指令重排序优化。重排序的意思是说CPU在执行指令时,为了进行优化处理,常常会对指令进行重新排序,也就是说CPU执行指令的顺序在重排序后是有可能和程序源码中的顺序是不一样的,但CPU并不会影响程序原有的运行结果。