美文网首页Java进阶
Java 技术之volatile

Java 技术之volatile

作者: 老羊_肖恩 | 来源:发表于2018-01-11 11:04 被阅读70次
java内存模型.png

volatile关键字的两层语义

  当一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰之后,那么就具备了两层语义:

  1. 保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。
  2. 禁止进行指令重排序。
volatile保证变量的可见性

  每个线程在运行过程中都有自己的工作内存,那么线程1在运行的时候,会将共享变量的值拷贝一份放在自己的工作内存当中。那么当线程2更改了共享变量的值之后,但是还没来得及写入主存当中,线程2转去做其他事情了,那么此时线程1是不知道当前的共享变量已经被线程2修改了,如果此时线程1也修改了当前的共享变量的值,那么最终主内存的值可能不是两个线程所期望的值。
  但是用volatile修饰之后就变得不一样了,因为:

  1. 使用volatile关键字会强制将修改的值立即写入主存;
  2. 使用volatile关键字的话,当线程2进行修改时,会导致线程1的工作内存中缓存的共享变量的缓存行无效(反映到硬件层的话,就是CPU的L1或者L2缓存中对应的缓存行无效);
  3. 由于线程1的工作内存中缓存的共享变量的缓存行无效,所以线程1再次读取共享变量的值时会去主存读取。

  那么在线程2修改共享变量的值时(当然这里包括2个操作,修改线程2工作内存中的值,然后将修改后的值写入内存),会使得线程1的工作内存中缓存的共享变量的缓存行无效,然后线程1读取时,发现自己的缓存行无效,它会等待缓存行对应的主存地址被更新之后,然后去对应的主存读取最新的值。那么此时线程1读取到的就是最新的正确的值。以上可以参考上图的Java内存模型。
  但是需要注意的是,volatile关键字并不能保证操作的原子性。

volatile 禁止指令重排序

  在前面提到volatile关键字能禁止指令重排序,所以volatile能在一定程度上保证有序性。volatile关键字禁止指令重排序有两层意思:
1)当程序执行到volatile变量的读操作或者写操作时,在其前面的操作的更改肯定全部已经进行,且结果已经对后面的操作可见;在其后面的操作肯定还没有进行;
2)在进行指令优化时,不能将在对volatile变量访问的语句放在其后面执行,也不能把volatile变量后面的语句放到其前面执行。

volatile不保证操作的原子性

  volatile关键字能保证可见性没有错,但是上面的程序错在没能保证原子性。可见性只能保证每次读取的是最新的值,但是volatile没办法保证对变量的操作的原子性。
  我们知道自增操作是不具备原子性的,它包括读取变量的原始值、进行加1操作、写入工作内存。也就是说自增操作的三个子操作可能会分割开执行,就有可能导致下面这种情况出现:
  假如某个时刻变量inc的值为10,线程1对变量进行自增操作,线程1先读取了变量inc的原始值,然后线程1被阻塞了;
  然后线程2对变量进行自增操作,线程2也去读取变量inc的原始值,由于线程1只是对变量inc进行读取操作,而没有对变量进行修改操作,所以不会导致线程2的工作内存中缓存变量inc的缓存行无效,所以线程2会直接去主存读取inc的值,发现inc的值时10,然后进行加1操作,并把11写入工作内存,最后写入主存。
  然后线程1接着进行加1操作,由于已经读取了inc的值,注意此时在线程1的工作内存中inc的值仍然为10,所以线程1对inc进行加1操作后inc的值为11,然后将11写入工作内存,最后写入主存。
  那么两个线程分别进行了一次自增操作后,inc只增加了1。
  解释到这里,可能有朋友会有疑问,不对啊,前面不是保证一个变量在修改volatile变量时,会让缓存行无效吗?然后其他线程去读就会读到新的值,对,这个没错。这个就是上面的happens-before规则中的volatile变量规则,但是要注意,线程1对变量进行读取操作之后,被阻塞了的话,并没有对inc值进行修改。然后虽然volatile能保证线程2对变量inc的值读取是从内存中读取的,但是线程1没有进行修改,所以线程2根本就不会看到修改的值。根源就在这里,自增操作不是原子性操作,而且volatile也无法保证对变量的任何操作都是原子性的。

volatile的原理和实现机制

  前面讲述了源于volatile关键字的一些使用,下面我们来探讨一下volatile到底如何保证可见性和禁止指令重排序的。下面这段话摘自《深入理解Java虚拟机》:
“观察加入volatile关键字和没有加入volatile关键字时所生成的汇编代码发现,加入volatile关键字时,会多出一个lock前缀指令”
  lock前缀指令实际上相当于一个内存屏障(也成内存栅栏),内存屏障会提供3个功能:
  1)它确保指令重排序时不会把其后面的指令排到内存屏障之前的位置,也不会把前面的指令排到内存屏障的后面;即在执行到内存屏障这句指令时,在它前面的操作已经全部完成;
  2)它会强制将对缓存的修改操作立即写入主存;
  3)如果是写操作,它会导致其他CPU中对应的缓存行无效。

参考:http://www.cnblogs.com/dolphin0520/p/3920373.html

相关文章

  • Java 技术之volatile

    volatile关键字的两层语义   当一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰之后,...

  • Java Volatile transient 关键字

    Java Volatile transient 关键字 java关键字volatile Volatile修饰的成员...

  • 后端知识- 收藏集 - 掘金

    Java多线程干货系列—(四)volatile关键字| 掘金技术征文 - 掘金原本地址:Java多线程干货系列—(...

  • java多线程并发技术之Volatile

    前言: 这个volatile真的是折磨我了很久,因为对于Android开发来说确实不是很重要,但是面试啊什么的会经...

  • Volatile理解

    Java Volatile1. volatile 理解2. volatile 不保证原子性3. Volatile ...

  • JAVA并发编程(一):理解volatile关键字

    Java中volatile这个热门的关键字,在面试中经常会被提及,在各种技术交流群中也经常被讨论:volatile...

  • java之volatile

    1. volatile简介 synchronized是阻塞式同步,在线程竞争激烈的情况下会升级为重量级锁。而vol...

  • Java之volatile

    先看一个例子: 虽然在变量numb上使用了volatile,但是得出的结果是不固定的,这说明,volatile是一...

  • volatile关键字

    volatile keyword example How to use volatile keyword in java

  • 深入剖析Java关键字之synchronized(原理篇)

    一、摘要  在《深入剖析Java关键字之volatile》的文章中,我们知道volatile关键字能够解决多线程编...

网友评论

    本文标题:Java 技术之volatile

    本文链接:https://www.haomeiwen.com/subject/uvubyxtx.html