Volatile语义的一些探讨

本文深入探讨了Java内存模型(JMM)中volatile关键字的两大核心特性:可见性和禁止CPU指令重排序。通过对具体示例的分析,解释了如何利用volatile确保变量在多线程环境下的正确同步。

  好吧,这里我只想说说volatile在JMM中的语义。

 

  当我们在使用volatile的时候,实际上它表达了下面那么些意思。

 

   1. 可见性。

      这个是大多数人都知道的一个特质, JAVA的线程有自己的工作内存区,与主存区不同,当我们对变量使用了volatile后,那么不管对这个变量的读或写,都会在主存中进行,而不会在处理器的缓存或者寄存器中进行。这个很好理解。

 

 

   2. 禁止CPU指令的重排序

      这个特质的理解稍微要花点脑细胞, 首先我们需要一点premilinary, 当我们的程序编写好了以后,会被翻译成指令集并被加载到内存中去运行。但是,在CPU真正执行的时候,处于性能方面的考虑,这些指令的执行不一定会按照程序中的顺序进行,只要保证其程序执行语义没有变化即可。

 

     来看下代码,

class VolatileExample {
  int x = 0;
  volatile boolean v = false;

  //in thread A
  public void writer() {
    x = 42;
    v = true;
  }

  //in thread B
  public void reader() {
    if (v == true) {
      //uses x - can we see x is 42?
    }
  }
}
 

    在这种情况下,当thread A 执行完后,在thread B中能看到x等于42吗?(变量v肯定可以看到,根据可见性可以推断出来) 好吧,在旧的JMM模型下,答案是不一定。原因就是CPU指令在执行时的重排序。在旧的JMM模型下,只规定了volatile变量和volatile变量之间不能进行重排序,但是并没有保证volatile变量和non-volatile变量之间不能进行重排序,所以, 当在thread A中,指令的执行可能是:

 

v = true;
x = 42;

 这样,当thread B 看到v为true的时候,x实际上还没有执行,所以值不是42.

 

 

   慢!眼尖的同学可能看出来了, 你说的这个跟重排序实际上没有关系呀,这个应该算是变量x的可见性问题,因为变量x不是声明为volatile的。

 

   好吧,我承认我偷懒了,在描述volatile变量可见性特质的时候,在新的JMM模型下,当对volatile变量进行写的时候,该线程(这里是thread A)所能看到变量(比如说变量x),都会一起刷新到主存中。这个也就是为什么我们会说对volatile变量的写操作,实际上等价于使用了synchronized关键字后释放monitor时产生的效果。 在这个前提下,上面的问题的确是CPU指令重排序的问题。

 

   但是幸运的是,JMM随后提出了happen-before原则来fix了这个问题(主要是volitale变量和non-volatile变量之间的重排序问题。)

    这里我只挑跟这个问题相关的三条原则来进行讲解,其余的可以到官方文档去查看。

 

    1. 单线程原则, 在单线程执行的环境下,指令的执行是跟程序代码的执行顺序一致。 对于上面的例子来说,在程序代码顺序上,x=42 先于 v=true, 那么在内存指令执行的时候也是如此。

 

    2. volatile变量原则,对volatile变量的写操作要优先于对volatile变量的读操作。

    3. 转递性原则,如果A操作先于B操作,B操作先于C操作,那么A操作肯定先于C操作。

 

    还是上面的例子,先用单线程原则,可以判断出,在thread A的执行中, x=42肯定要优先于v=true进行执行, 而在thread B的执行中,对v的读取操作肯定要优先于对x的使用操作。

    接着再使用volatile变量原则,可以判断,对v的写肯定要先于对v的读, 最后再根据转递性原则, 可以推出在thread A中x=42的赋值操作肯定要先于thread B中对x的使用, 也就是说,当v读取出来是为true的时候,x肯定是42. 指令不会进行重排序。

 

那如果我们将x=42,v=true的语句倒过来呢?

 

class VolatileExample {
  int x = 0;
  volatile boolean v = false;
  public void writer() {
    //颠倒赋值给x,v的顺序。
    v = true;
    x = 42;
  }

  public void reader() {
    if (v == true) {
      //uses x - can we see x is 42 here?
    }
  }
}
 

我想通过上面的分析,各位同学自己应该也能推断出来了吧。:)

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值