JUC之volatite关键字

在之前对单例模式的介绍中,我提到了volatite,在那篇文章中,我们使用volatite是为了保证线程安全,那么volatite是否能够完全保证线程安全呢?

首先,线程安全需要考虑以下三个方面:

  1. 可见性:一个线程对共享变量进行修改,另一个线程能看到最新结果
  2. 有序性:一个线程内代码按编写顺序执行
  3. 原子性:一个线程内多行代码以一个整体运行,期间不能有其他线程的代码插队

直接说结论,volatite可以保证共享变量的可见性和有序性,但是不能保证原子性。

为什么不能保证原子性?

举个例子,对volatite修饰的变量进行加或减操作时,会进行四步操作:

  1. 取出该变量的值
  2. 取出要增加的数
  3. 把两者相加(减),从而得到和(差)
  4. 把和(差)赋值给该变量

对变量用volatite修饰,并不能保证该变量在执行加或者减操作时,并不能保证四个步骤是一次性连着完成的。这就无法保证原子性了。

volatite保证可见性的原理

当我们没有选择-Xint选项时,JIT可能会对我们的代码进行优化。如果JIT发现我们长时间特别多次地读取一个变量的值都一样,就会“帮助”我们优化这段代码,在这之后,我们对这个值进行修改的时候,我们的代码就不一定能见到值的修改,因为JIT以及优化了这段代码,这个变量在这段代码的机器码中可能已经是一个定值了。这就无法保证可见性了。

而选择-Xint,则是在告诉虚拟机,我只想用解释器,而不使用JIT优化,从而避免了这个问题。

但是选择-Xint有个很大的缺点是,这意味着整个代码都没有选用JIT优化,从而严重降低了代码的执行效率。

这个时候,使用volatite关键字修饰变量,就可以很好地解决这个问题:这意味着告诉虚拟机,针对这个变量,不进行JIT优化,这就保证了可见性,也保证了效率。

volatite保证有序性的原理

volatite是使用内存屏障保证有序性的。使用内存屏障后,我们可以保证对“写操作”,“对修饰的变量的写操作”一定晚于“原代码中早于‘对修饰的变量的写操作’的写操作”;对“读操作”,“对修饰变量的读操作”一定早于“源代码中晚于’对修饰变量的读操作‘的读操作”。看起来好像有点绕口,我们用代码举个例子:

假设我们对c变量用volatite关键字进行修饰,而a和b两个变量则没有:

1
2
3
a = 1;
b = 2;
c = 3;

不管a = 1b = 2的执行顺序怎么样,c = 3一定在两者之后执行。

1
2
3
d = c;
e = b;
f = a;

不管e = bf = a的执行顺序怎么样,d = c一定在两者之前执行。


JUC之volatite关键字
https://jlqusername.github.io/2025/04/08/JUC之volatite关键字/
作者
B907
发布于
2025年4月8日
许可协议