voliate关键字原理

2022-08-15 20:30:33 浏览数 (1)

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

被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),或者能够确保只有单一的线程修改变量的值
  • 变量不需要与其他的状态变量共同参与不变性约束(即该变量不和其他变量关联)

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

0 人点赞