深入理解JVM——volatile in Java

第一次面试时,interviewer问“你对volatile了解多少?”由于interviewer的发音(英:[ˈvɒlətaɪl]))有些不准,笔者很傻地问了好几遍“您说什么?”,然后…直接GG了。volatile是JVM提供的最轻量级的同步机制,作为应届生面试中常见的关键字考察点,一块来学习下吧~


  • Java内存模型
  • volatile语义:
    • 可见性
    • 禁止重排序
    • 无法保证一致性
  • 应用场景

一、Java内存模型

1、Java内存模型目的是在JVM中将变量(线程共享的实例字段,静态字段和构成数组对象的元素等而非线程私有的局部变量和方法参数)存储到内存和从内存中取出变量的细节实现。
2、所有的变量存储在主内存(Main Memory),线程有各自的工作内存(working Memory):用以存储该线程所需使用的主内存的副本拷贝,完成对变量的操作。线程之间变量值的传递均需通过主内存来完成。所有线程都在主内存中完成值得修改操作为啥不行呢?嘿嘿,直接操作主内存太慢了!JVM才利用性能较高的工作内存,你可以类比下高速缓存、CPU和内存之间的关系。
3、 深入理解JVM——volatile in Java
4、考虑这样一个场景:主内存中i = 1;现有A和B两个线程均对i进行自增操作,期待结果是i = 3。然而在没有进行“同步”机制处理的情况下可能发生:A、B同时从主存中读取i分别在工作内存中自增操作,然后写回主存,此时的主存i = 2,并非正确结果。那怎么办呢?


二、volatile可见性

1、含义:当某线程修改了某变量的值,修改后的值对其他线程来说立即可见。
2、栗子:i = 1;
普通情况下,i 在某一线程中被修改后何时读入主内存无法确定。当别的线程去主内存读取i时,可能还是原值。若 i 被volatile 修饰,当ThreadA读取i并自增操作,新值会立即同步到主内存中,即使其他线程对原值做缓存也无效。那么。其中的原理是什么呢?
为什么volatile关键字可以有这样的特性?这得益于java语言的先行发生原则(happens-before)。*:
In computer science, the happened-before relation is a relation between the result of two events, such that if one event should happen before another event, the result must reflect that, even if those events are in reality executed out of order (usually to optimize program flow).
翻译结果如下:
在计算机科学中,先行发生原则是两个事件的结果之间的关系,如果一个事件发生在另一个事件之前,结果必须反映,即使这些事件实际上是乱序执行的(通常是优化程序流程)。
这里所谓的事件,实际上就是各种指令操作,比如读操作、写操作、初始化操作、锁操作等等。
先行发生原则作用于很多场景下,包括同步锁、线程启动、线程终止、volatile。我们这里只列举出volatile相关的规则:
对于一个volatile变量的写操作先行发生于后面对这个变量的读操作。


三、volatile禁止重排序

1、重排序含义:CPU为了提高程序的运行效率,对原序输入的代码进行顺序优化,但它保证程序的最终执行结果与原序执行结果一致。譬如你在上午放学后,为了精力充沛地完成下午的课堂。需要进行1、吃饭;2、洗头;3、睡午觉;由于食堂排队的人多,你的大脑(CPU)可以为了提高效率,选择了213的执行顺序,但不影响“精力充沛地完成下午的课堂”这个最终结果。为了CPU的执行效率而对代码顺序进行优化的一种操作。
2、volatile 禁止重排序含义:


  • 保证volatile变量前的操作必须全部进行完毕且其结果对volatile变量的后续操作可见,volatile变量后续操作必须还未进行。
  • 栗子:
    • a = 1;
    • b = 2;
    • volatileC = 3;
    • a = 4;
    • b = 5;
      则在执行到volatileC时,保证前两个操作已完成,后两个操作未完成。至于前两个操作之间的执行顺序可以进行重排。
      如果对声明了volatileC进行写操作时,JVM会向CPU发送一条Lock前缀的指令(内存屏障),Lock确保重排序优化时将volatileC前后的指令隔开。

什么是内存屏障?内存屏障(Memory Barrier)是一种CPU指令,*给出了如下定义:
A memory barrier, also known as a membar, memory fence or fence instruction, is a type of barrier instruction that causes a CPU or compiler to enforce an ordering constraint on memory operations issued before and after the barrier instruction. This typically means that operations issued prior to the barrier are guaranteed to be performed before operations issued after the barrier.
翻译结果如下:
内存屏障也称为内存栅栏或栅栏指令,是一种屏障指令,它使CPU或编译器对屏障指令之前和之后发出的内存操作执行一个排序约束。 这通常意味着在屏障之前发布的操作被保证在屏障之后发布的操作之前执行。


四、volatile不保证一致性


  • 何为一致性?
    • 原子性
    • 有序性
    • 可见性

volatile 无法保证原子性,即操作要么全部执行要么都不执行。
栗子:volatileC = 1;
threadB读取该值后自增,由于threadA只读取还没来得及自增修改,所以threadB的工作内存中的缓存变量volatileC的缓存行仍有效并且主存中的值不会被刷新。threadB完成自增后将值11写入工作内存并写入主存。
由于threadA已读取了volatileC的值(1),则自增后(2)写入工作内存和主存,最终volatileC为2。


五、总结

作为JVM中最轻量级的同步机制,volatile要比锁弱一级。
锁:threadA获取锁后,其他线程无法对其进行读取更别提修改等操作,
volatle:threadA获取volatile 变量后会让该变量的修改对其他线程都可见并且其他线程仍可对该变量进行读取修改。


六、应用场景
面试时经常会被问及的一个问题:单例模式中的double check 双重锁。
深入理解JVM——volatile in Java

上述的三个步骤在JVM即时编译器中存在指令重排序,若步骤3先于2执行,即执行顺序为132时,在2执行前就会返回instance(因为线程3执行后instance就不是null了),此时会报错。
ps:关于volatile的介绍,《深入理解Java虚拟机》这本书介绍很详尽。有兴趣的同学可以去看看。