volatile的理解

简介: 存在背景 缓存不一致性问题:在多核CPU中,每条线程可能运行于不同的CPU中,因此每个线程运行时有自己的高速缓存(对单核CPU来说,其实也会出现这种问题,只不过是以线程调度的形式来分别执行的)。 解决方法: 通过在总线加LOCK#锁的方式   通过缓存一致性协议   CPU和其他部件进行通信都是通过总线来进行的,对总线加LOCK#锁,阻塞了其他CPU对其他部件访问(如内存),只能有一个CPU能使用这个变量的内存。

存在背景

缓存不一致性问题:在多核CPU中,每条线程可能运行于不同的CPU中,因此每个线程运行时有自己的高速缓存(对单核CPU来说,其实也会出现这种问题,只不过是以线程调度的形式来分别执行的)。

解决方法:

  • 通过在总线加LOCK#锁的方式  
  • 通过缓存一致性协议  

CPU和其他部件进行通信都是通过总线来进行的,对总线加LOCK#锁,阻塞了其他CPU对其他部件访问(如内存),只能有一个CPU能使用这个变量的内存。

最出名的就是Intel的MESI协议,MESI协议保证了每个缓存中使用的共享变量的副本是一致的。它核心的思想是:当CPU写数据时,如果发现操作的变量是共享变量,即在其他CPU中也存在该变量的副本,会发出信号通知其他CPU将该变量的缓存行置为无效状态,因此当其他CPU需要读取这个变量时,发现自己缓存中缓存该变量的缓存行是无效的,那么它就会从内存重新读取。

用法解释

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

  1)保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。

  2)禁止进行指令重排序(他上面的比他先执行,他下面的比他后执行,但并不保证上面和下面的代码块也是有序执行)

保证可见性:

  volatile修饰的成员变量在每次被线程访问时,都强迫从共享内存中重读该成员变量的值。而且,当成员变量发生变化时,强迫线程将变化值回写到共享内存。这样在任何时刻,两个不同的线程总是看到某个成员变量的同一个值。

不保证原子性:

  譬如inc++,自增操作是不具备原子性的,它包括读取变量的原始值、进行加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变量时,会让缓存行无效,然后其他线程去读就会读到新的值。但是要注意,线程1对变量进行读取操作之后被阻塞了,并没有对inc值进行修改。然后虽然volatile能保证线程2对变量inc的值读取是从内存中读取的,但是线程1没有进行修改,所以线程2根本就不会看到修改的值。根源就在这里,自增操作不是原子性操作,而且volatile也无法保证对变量的任何操作都是原子性的。

一定程度上保证有序性:

  volatile关键字禁止指令重排序有两层意思:

  1)当程序执行到volatile变量的读操作或者写操作时,在其前面的操作的更改肯定全部已经进行,且结果已经对后面的操作可见;在其后面的操作肯定还没有进行;

  2)在进行指令优化时,不能将在对volatile变量访问的语句放在其后面执行,也不能把volatile变量后面的语句放到其前面执行。

原理解释

JVM角度:

  Java使用一个主内存来保存变量当前值,而每个线程则有其独立的工作内存。

  线程访问变量的时候会将变量的值拷贝到自己的工作内存中,这样,当线程对自己工作内存中的变量进行操作之后,就造成了工作内存中的变量拷贝的值与主内存中的变量值不同

  Java语言规范中指出:为了获得最佳速度,允许线程保存共享成员变量的私有拷贝,而且只当线程进入或者离开同步代码块时才与共享成员变量的原始值对比。

  这样当多个线程同时与某个对象交互时,就必须要注意到要让线程及时的得到共享成员变量的变化。

  而volatile关键字就是提示VM:对于这个成员变量不能保存它的私有拷贝,会使在工作内存中的私有拷贝失效,从而再去主内存取值。

汇编角度:

  “观察加入volatile关键字和没有加入volatile关键字时所生成的汇编代码发现,加入volatile关键字时,会多出一个lock前缀指令”

  lock前缀指令实际上相当于一个内存屏障(也成内存栅栏),内存屏障会提供3个功能:

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

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

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

使用条件:

通常来说,使用volatile必须具备以下2个条件:

  1)对变量的写操作不依赖于当前值

  2)该变量没有包含在具有其他变量的不变式中

实际上,这些条件表明,可以被写入 volatile 变量的这些有效值独立于任何程序的状态,包括变量的当前状态。

事实上,我的理解就是上面的2个条件需要保证操作是原子性操作,才能保证使用volatile关键字的程序在并发时能够正确执行。

 

下面列举几个Java中使用volatile的几个场景。

1.状态标记量

volatile   boolean   flag =   false  ;
 //!flag是原子性操作,修改flag后即可见
while  (!flag){
      doSomething();
}
 
public   void   setFlag() {
      flag =   true  ;

    }

volatile   boolean   inited =   false  ;
//线程1:
context = loadContext();  
//保证运行 inited =   true  ;之前已被初始化,用到了一定程度的有序性
inited =   true  ;            
 
//线程2:
while  (!inited ){
sleep()
}
doSomethingwithconfig(context);

 

 

目录
相关文章
|
计算机视觉
Mac-Mini 外接三个显示器教程
Mac-mini 官方只支持连接2个显示器,ctrl cv 复制粘贴代码和写博客的时候十分不方便,这时候需要外接第三个显示器,下面看看如何操作。
1716 0
Mac-Mini 外接三个显示器教程
【全是精华】Token的获取和使用-FastApi版
【全是精华】Token的获取和使用-FastApi版
1491 0
|
弹性计算 Linux 对象存储
阿里云国际版如何将ECS云服务器中的数据备份到本地
阿里云国际版如何将ECS云服务器中的数据备份到本地
|
9月前
|
开发框架 缓存 搜索推荐
PiliPala:开源项目真香,B站用户狂喜!这个开源APP竟能自定义主题+去广告?PiliPala隐藏功能大揭秘
嗨,大家好,我是小华同学。PiliPala 是一个基于 Flutter 开发的 BiliBili 第三方客户端,提供流畅、个性化的使用体验。核心功能包括视频浏览与推荐、用户互动、丰富的播放设置、多维度搜索和个性化主题等。相比官方客户端,PiliPala 功能更丰富、性能更优、界面更美观。
478 14
|
11月前
|
SQL 安全 网络安全
网络安全与信息安全:关于网络安全漏洞、加密技术、安全意识等方面的知识分享
随着互联网的普及,网络安全问题日益突出。本文将从网络安全漏洞、加密技术和安全意识三个方面进行探讨,旨在提高读者对网络安全的认识和防范能力。通过分析常见的网络安全漏洞,介绍加密技术的基本原理和应用,以及强调安全意识的重要性,帮助读者更好地保护自己的网络信息安全。
223 10
|
JavaScript
Vue 的响应式原理中 Object.defineProperty 有什么缺陷
Vue 的响应式原理主要依赖于 `Object.defineProperty`,但该方法存在一些缺陷:无法检测到对象属性的添加和删除,且对大量数据进行代理时性能较差。Vue 3 中改用了 Proxy 来解决这些问题。
|
机器学习/深度学习 存储 分布式计算
HDFS Namenode HA高可用搭建
HDFS Namenode HA高可用搭建
547 1
|
机器学习/深度学习 JavaScript 数据挖掘
【理论+案例实战】Python数据分析之逻辑回归(logistic regression)
逻辑回归是分类当中极为常用的手段,它属于概率型非线性回归,分为二分类和多分类的回归模型。对于二分类的logistic回归,因变量y只有“是”和“否”两个取值,记为1和0。假设在自变量x1,x2,……,xp,作用下,y取“是”的概率是p,则取“否”的概率是1-p。
15146 0
|
安全 Linux 测试技术
alpha、beta、rc这些发布的版本各自含义
alpha、beta、rc这些发布的版本各自含义
724 0
alpha、beta、rc这些发布的版本各自含义
|
编解码
【FFmpeg】ffmpeg 命令查询三 ( 查询 ffmpeg 命令分类支持的参数 )
【FFmpeg】ffmpeg 命令查询三 ( 查询 ffmpeg 命令分类支持的参数 )
540 0
【FFmpeg】ffmpeg 命令查询三 ( 查询 ffmpeg 命令分类支持的参数 )