voliate关键字原理

voliate关键字原理被volatile修饰的变量在编译成字节码文件时会多个lock指令,该指令在执行过程中会生成相应的内存屏障,以此来解决可见性跟重排序的问题。voliate关键字作用静止重排序保证变量赋值操作的顺序与程序代码中的执行顺序一致。线程可见性原理使用场景…

大家好,又见面了,我是你们的朋友全栈君。

被volatile修饰的变量在编译成字节码文件时会多个lock指令,该指令在执行过程中会生成相应的内存屏障,以此来解决可见性跟重排序的问题。

预备知识
  • 指令重排序
    • 为什么到指令重排序:一般来说,处理器为了提高程序运行效率,可能会对输入代码进行优化,它不保证程序中各个语句的执行先后顺序同代码中的顺序一致,但是它会保证程序最终执行结果和代码顺序执行的结果是一致的。
    • 指令重排序遵守的准则:编译器和处理器在重排序时,会遵守数据依赖性,编译器和处理器不会改变存在数据依赖关系的两个操作的执行顺序。
    • 什么办法来禁止指令重排序呢:添加内存屏障
  • 内存屏障
    • 内存屏障分类:内存屏障分为两种:Load Barrier 和 Store Barrier即读屏障和写屏障。
    • 内存屏障作用:
      (1)阻止屏障两侧的指令重排序,即屏障下面的代码不能和屏障上面的代码交换顺序(静止重排序)
      (2)在有内存屏障的地方,线程修改完共享变量以后会马上把该变量从本地内存写回到主内存,并且让其他线程本地内存中该变量副本失效(使用MESI协议)(线程可见性)
      对于Load Barrier来说,在指令前插入Load Barrier,可以让高速缓存中的数据失效,强制从新从主内存加载数据;
      对于Store Barrier来说,在指令后插入Store Barrier,能让写入缓存中的最新数据更新写入主内存,其他线程程可见
voliate关键字作用
  • 静止重排序: voliate修饰的变量,保证变量赋值操作的顺序与程序代码中的执行顺序一致

  • 线程可见性: 当一条线程修改了voliate变量的值,新值对于其他线程来说是可以立即得知的

原理
  • volatile关键字修饰的变量会存在一个“lock:”的前缀,Lock不是一种内存屏障,但是它能完成类似内存屏障的功能。Lock会对CPU总线或高速缓存加锁(一般只是对缓存行枷锁),可以理解为CPU指令级的一种锁。
  • 在具体的执行上,它先对总线或缓存加锁,然后执行后面的指令,在Lock锁住总线的时候,其他CPU的读写请求都会被阻塞,直到锁释放。最后释放锁后会把高速缓存中的脏数据(修改过的数据)全部刷新回主内存,且这个写回内存的操作会使在其他CPU里缓存了该地址的数据无效。
  • 在java内存层面可以理解为:当需要使用(use)这个变量时,必须从主存中read–>load这个变量(即要使用这个变量时,必须从主存中读取这个变量,这就保证了该变量是最新的);当线程工作内存中这个变量被赋值时(assign),那么立刻store–>write这个变量(即当该值计算完成,立刻把这个变量写会主存,并且使得该值在其他内存的工作变量中无效)
java内存屏障

在这里插入图片描述

volatile语义中的内存屏障

volatile的内存屏障策略非常严格保守,非常悲观且毫无安全感的心态:

  • 在每个volatile写操作前插入StoreStore屏障(这个屏障前后的2个Store指令不能交换顺序),在写操作后插入StoreLoad屏障(这个屏障前后的2个Store Load指令不能交换顺序);
  • 在每个volatile读操作前插入LoadLoad屏障(这个屏障前后的2个Load指令不能交换顺序),在读操作后插入LoadStore屏障(这个屏障前后的2个Load Store指令不能交换顺序);

由于内存屏障的作用,避免了volatile变量和其它指令重排序、线程之间实现了通信,使得volatile表现出了锁的特性。
在Java中对于volatile修饰的变量,编译器在生成字节码时,会在指令序列中插入内存屏障禁止处理器重排序。

举例

两条线程Thread-A与Threab-B同时操作主存中的一个volatile变量i时。Thread-A写了变量i,那么:
Thread-A发出LOCK#指令
(1)发出的LOCK#指令锁总线(或锁缓存行)(因为它会锁住总线,导致其他CPU不能访问总线,不能访问总线就意味着不能访问系统内存),然后释放锁,最后刷新回主内(瞬间完成的,写回时候其他缓存行失效),同时让Thread-B高速缓存中的缓存行内容失效。
(2)Thread-A向主存回写最新修改的i
Thread-B读取变量i,那么:
Thread-B发现对应地址的缓存行被锁了,等待锁的释放,缓存一致性协议会保证它读取到最新的值(重新从主存读)
由此可以看出,volatile关键字的读和普通变量的读取相比基本没差别,差别主要还是在变量的写操作上。

举例

在这里插入图片描述

使用场景

满足以下两点,那么volatile修饰的共享变量,不用加锁也能保证线程安全:

  • 运算结果不依赖变量的当前值(即变量计算的结果和当前的值没有关系,比如一个boolean变量的改变,但是i++这种运算就存在依赖关系,以为新值是在旧值的基础上加1),或者能够确保只有单一的线程修改变量的值
  • 变量不需要与其他的状态变量共同参与不变性约束(即该变量不和其他变量关联)
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/134267.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)


相关推荐

  • jdk11的zgc_开源jdk

    jdk11的zgc_开源jdkJDK11ZGC简介注1:本文翻译自这篇"文章"注2:我有了新的独立博客"地址",欢迎访问前言ZGC是最近由Oracle为OpenJDK开源

  • java date转毫秒_原单位要求退回奖金

    java date转毫秒_原单位要求退回奖金通过自定义的一个子类继承JsonSerializer类然后重写里面的方法publicvoidserialize(Datedate,JsonGeneratorjsonGenerator,SerializerProviderserializerProvider)throwsIOException之后我们在需要将Date转换成long的实体类中添加注解@JsonSe…

  • Oracle函数之LAG函数[通俗易懂]

    Oracle函数之LAG函数[通俗易懂]语法使用方法  LAG是一个分析函数。它可以在不使用自连接的情况下同时访问到一个表的多行数据。给一个或多个列名和一个游标位置(位移),LAG可以访问当前行之前的行,行之间间隔的行数为位移值。  语法树中的offset(位移)参数是可选的,可以指定一个大于0的整数,如果不指定offset(位移)参数函数会默认位移为1。语法树中的default值也是可选的,这个default值是当位移值超过查…

  • DDoS攻击工具HOIC分析

    DDoS攻击工具HOIC分析本文是绿盟科技安全+技术刊物中的文章,文章对拒绝服务攻击工具—”HighOrbitIonCannon”的技术性分析。HOIC是一款用RealBasic开发可移植的多平台拒绝服务攻击工具,该工具虽然对使用者的水平…

  • 中介模式和学习日记Effective C++

    中介模式和学习日记Effective C++

  • Java发送邮件换行问题

    Java发送邮件换行问题利用用java编写的发送邮件要使用来实现换行,在邮件中内容自动实现换行而不能使用一般的\n,或者\r\n

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号