JUC之volatite关键字
在之前对单例模式的介绍中,我提到了volatite
,在那篇文章中,我们使用volatite
是为了保证线程安全,那么volatite
是否能够完全保证线程安全呢?
首先,线程安全需要考虑以下三个方面:
- 可见性:一个线程对共享变量进行修改,另一个线程能看到最新结果
- 有序性:一个线程内代码按编写顺序执行
- 原子性:一个线程内多行代码以一个整体运行,期间不能有其他线程的代码插队
直接说结论,volatite
可以保证共享变量的可见性和有序性,但是不能保证原子性。
为什么不能保证原子性?
举个例子,对volatite
修饰的变量进行加或减操作时,会进行四步操作:
- 取出该变量的值
- 取出要增加的数
- 把两者相加(减),从而得到和(差)
- 把和(差)赋值给该变量
对变量用volatite
修饰,并不能保证该变量在执行加或者减操作时,并不能保证四个步骤是一次性连着完成的。这就无法保证原子性了。
volatite保证可见性的原理
当我们没有选择-Xint
选项时,JIT可能会对我们的代码进行优化。如果JIT发现我们长时间特别多次地读取一个变量的值都一样,就会“帮助”我们优化这段代码,在这之后,我们对这个值进行修改的时候,我们的代码就不一定能见到值的修改,因为JIT以及优化了这段代码,这个变量在这段代码的机器码中可能已经是一个定值了。这就无法保证可见性了。
而选择-Xint
,则是在告诉虚拟机,我只想用解释器,而不使用JIT优化,从而避免了这个问题。
但是选择-Xint
有个很大的缺点是,这意味着整个代码都没有选用JIT优化,从而严重降低了代码的执行效率。
这个时候,使用volatite
关键字修饰变量,就可以很好地解决这个问题:这意味着告诉虚拟机,针对这个变量,不进行JIT优化,这就保证了可见性,也保证了效率。
volatite保证有序性的原理
volatite
是使用内存屏障保证有序性的。使用内存屏障后,我们可以保证对“写操作”,“对修饰的变量的写操作”一定晚于“原代码中早于‘对修饰的变量的写操作’的写操作”;对“读操作”,“对修饰变量的读操作”一定早于“源代码中晚于’对修饰变量的读操作‘的读操作”。看起来好像有点绕口,我们用代码举个例子:
假设我们对c变量用volatite
关键字进行修饰,而a和b两个变量则没有:
1 |
|
不管a = 1
和b = 2
的执行顺序怎么样,c = 3
一定在两者之后执行。
1 |
|
不管e = b
和f = a
的执行顺序怎么样,d = c
一定在两者之前执行。