关于volatile关键字的一点小总结

一、volatile的实现机制

  • 内存屏障:
    • 它确保指令重排序时不会把其后面的指令排到内存屏障之前的位置,也不会把前面的指令排到内存屏障的后面;即在执行到内存屏障这句指令时,在它前面的操作已经全部完成;

    • 它会强制将对缓存的修改操作立即写入主存;

    • 如果是写操作,它会导致其他CPU中对应的缓存行无效。

二、有序性

  • 有序性:即程序执行的顺序按照代码的先后顺序执行。
int a = 1;              
boolean b = true;
  • 上面定义了2行代码,理论上来说a运行在b前面,但是真实情况不一定,因为JVM会去做一些优化,优化后就不一定a运行在b前面了;volatile会禁止指令重排序所以可以保证有序性。

三、可见性

  • 可见性是指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。volatile主要是通过内存屏障去实现的。使用volatile关键字会强制将修改的值立即写入主存,然后将其他工作内存中的数据声明为无效数据,当线程来取数据时发现自己的数据是无效的,于是就到主存取,实现缓存的一致性。所以能够保证其他线程可见性。

四、关于volatile原子性问题

  • 上面说了volatile是具备可见性的,简单猜想应该是具备原子性的。
  • 只是严格意义来讲,对任意单个volatile变量的读/写具有原子性(如boolean a = true;a=false;),但类似于运算(如i++、i=i*x等等)这种复合操作不具有原子性。
volatile int i = 0;
public void increase(){
   i++;
}
  • 上面的代码,假设有2个线程去调用它自增代码,线程A,B。2个线程都调用10次,最后的结果一定会比我们自己算的结果要小。这是为什么呢?这是因为这(i++)是个复合指令,而volatile内存屏障只在变量修改的那一个步骤指令触发,随后修改主存的值,把其他线程i的缓存值标记为无效(Invalid)。

  • i++的复合指令包括:

    • 首先读取i的值
    • 对i进行+1运算
    • 再给i赋值
  • 可以试想下这样的场景,线程A,B同时运行,此时线程A,B获取到的值都是0;线程A执行+1操作,然后赋值,此时内存屏障生效,把主存的值修改,并把其他线程i的缓存值标记为无效;线程B也是同样的(注意线程B i的值也是0),线程B再执行+1操作,然后赋值,此时内存屏障生效,把主存的值修改,并把其他线程i的缓存值标记为无效。这样子下去结果肯定会比我们自己算的要小。所以在运算的时候volatile保证不了原子性。

到这儿基本结束了,另外文章代码或者我理解有误的地方,希望能批评指出。

posted on 2018-10-10 21:56  愤怒的苹果ext  阅读(22)  评论(0编辑  收藏  举报

导航