volatile关键字是干什么的?他是怎样实现的?

Posted 流楚丶格念

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了volatile关键字是干什么的?他是怎样实现的?相关的知识,希望对你有一定的参考价值。

文章目录

volatile关键字作用:

1. 保证可见性

当写一个volatile变量时,JVM会把该线程本地内存中的变量强制刷新到主内存中去,这个写会操
作会导致其他线程中的volatile变量缓存无效。

2. 禁止指令重排

使用volatile关键字修饰共享变量可以禁止指令重排序,volatile禁止指令重排序有一些规则:

  • 当程序执行到volatile变量的读操作或者写操作时,在其前面的操作的更改肯定全部已经进行,且结果已经对后面的操作可见,在其后面的操作肯定还没有进行;
  • 在进行指令优化时,不能将对volatile变量访问的语句放在其后面执行,也不能把volatile变量后面的语句放到其前面执行。

即执行到volatile变量时,其前面的所有语句都执行完,后面所有语句都未执行。且前面语句的结
果对volatile变量及其后面语句可见。

注意,虽然volatile能够保证可见性,但它不能保证原子性。volatile变量在各个线程的工作内存中是不
存在一致性问题的,但是Java里面的运算操作符并非原子操作,这导致volatile变量的运算在并发下一样
是不安全的。

指令重排序是指源码顺序和程序顺序不一样,或者说程序顺序和执行的顺序不一致,重排序的对象是指令。指令重排序是编译器处于性能考虑,在不影响程序(单线程程序)正确性的条件下进行重新排序。指令重排序不是必然发生的,指令重排序会导致线程安全问题。指令重排序也被称为处理器的乱序执行,在这种情况下尽管指令的执行顺序可能没有完全按照程序顺序执行,但是由于指令的执行结果的提交(反应到寄存器和内存中),仍然是按照程序顺序来的,因此处理器的指令重排序并不会对单线程的正确性产生影响。指令重排序不会对单线程程序的正确性产生影响,但他可能导致多线程程序出现非预期结果。

volatile的实现原理

volatile可以保证线程可见性且提供了一定的有序性,但是无法保证原子性。

在JVM底层volatile是采用“内存屏障”来实现的。观察加入volatile关键字和没有加入volatile关键字时所生成的汇编代码发现,加入volatile关键字时,会多出一个lock前缀指令,lock前缀指令实际上相当于一个内存屏障,内存屏障会提供3个功能:

  1. 它确保指令重排序时不会把其后面的指令排到内存屏障之前的位置,也不会把前面的指令排到内存屏障的后面;即在执行到内存屏障这句指令时,在它前面的操作已经全部完成;
  2. 它会强制将对缓存的修改操作立即写入主存
  3. 如果是写操作,它会导致其他CPU中对应的缓存行无效

具体内存屏障流程

首先我们要知道Java中JMM(Java Memory Model)的概念:

Java 内存模型(JMM)是一种抽象的概念,并不真实存在,JMM描述了一组规则或规范,通过这组规范定义了程序中各个变量(包括实例字段、静态字段和构成数组对象的元素)的访问方式

JMM 试图屏蔽各种硬件和操作系统的内存访问差异,以实现让 Java 程序在各种平台下都能达到一致的内存访问效果。

Java内存模型规定了所有的变量都存储在主内存中,每条线程还有自己的工作内存,线程的工作内存中保存了该线程中是用到的变量的主内存副本拷贝,线程对变量的所有操作都必须在工作内存中进行,而不能直接读写主内存。

不同的线程之间也无法直接访问对方工作内存中的变量,线程间变量的传递均需要自己的工作内存和主存之间进行数据同步进行。

主内存与工作内存交互模式如下图所示:

主内存主要存储的是Java实例对象,所有线程创建的实例对象都存放在主内存中,不管该实例对象是成员变量还是方法中的本地变量(也称局部变量),当然也包括了共享的类信息、常量、静态变量。由于是共享数据区域,多条线程对同一个变量进行访问可能会发现线程安全问题。

工作内存每条线程都有自己的工作内存(Working Memory,又称本地内存,可与前面介绍的处理器高速缓存类比),线程的工作内存中保存了该线程使用到的变量的主内存中的共享变量的副本拷贝。工作内存是 JMM 的一个抽象概念,并不真实存在。它涵盖了缓存,写缓冲区,寄存器以及其他的硬件和编译器优化。

主要存储当前方法的所有本地变量信息(工作内存中存储着主内存中的变量副本拷贝),每个线程只能访问自己的工作内存,即线程中的本地变量对其它线程是不可见的,就算是两个线程执行的是同一段代码,它们也会各自在自己的工作内存中创建属于当前线程的本地变量,当然也包括了字节码行号指示器、相关Native方法的信息。

主内存和工作内存之间的交互有具体的交互协议,JMM定义了八种操作指令来完成,这八种操作是原子的、不可再分的,它们分别是:lock,unlock,read,load,use,assign,store,write

  • 其中lock,unlock,read,write作用于主内存
  • load,use,assign,store作用于工作内存

各个指令功能说明如下:

操作说明
(1) lock将主内存中的变量锁定,为一个线程所独占
(2) unclock将lock加的锁定解除,此时其它的线程可以有机会访问此变量
(3) read将主内存中的变量值读到工作内存当中
(4) load将read读取的值保存到工作内存中的变量副本中。
(5) use将值传递给线程的代码执行引擎
(6) assign将执行引擎处理返回的值重新赋值给变量副本
(7) store将变量副本的值存储到主内存中。
(8) write将store存储的值写入到主内存的共享变量当中。

volatile 底层实现主要是通过汇编lock前缀指令,它会锁定这块内存区域的缓存(缓存行锁定)并回写到主内存

对一个变量lock后,会清空该线程工作内存变量的值,重新执行load或者assign操作初始化工作内存中* 变量的值

工作流程如下:

  • 从主存复制变量到当前工作内存(read and load)
  • 执行代码,改变共享变量值 (use and assign)
  • 用工作内存数据刷新主存相关内容 (store and write)

各指令工作流程图如下所示:

各操作指令工作规则如下:

  • read 和 load、store和write必须成对出现
  • assign操作,工作内存变量改变后必须刷回主内存
  • 同一时间只能运行一个线程对变量进行lock,当前线程lock可重入,unlock次数必须等于lock的次数,* 该变量才能解锁。
  • 对一个变量lock后,会清空该线程工作内存变量的值,重新执行load或者assign操作初始化工作内存中* 变量的值。
  • unlock前,必须将变量同步到主内存(store/write操作)

以上是关于volatile关键字是干什么的?他是怎样实现的?的主要内容,如果未能解决你的问题,请参考以下文章

关键字: volatile详解

Java并发- 聊聊Volatile

Volatile是用于解决什么问题,谈谈实现原理

你真的了解 volatile 关键字吗?

Volatile关键字实现原理

volatile关键字