并发编程(六)volatile应用

 文章更新时间:2021/07/10

一、初认volatile

  首先学习volatile关键字时,我们先简单的了解一下它能干啥:

  工作内存与主内存同步延迟现象导致的可见性问题:

  • 可通过synchronized或volatile关键字解决,他们都可以使一个线程修改后的变量立即对其它线程可见

  对于指令重排导致的可见性问题和有序性问题:

  • 可以使用volatile关键字解决,因为volatile关键字的另一个作用就是禁止重排序优化

volatile的一些特点

  • volatile可以解决可见性和有序性,但不能解决原子性
  • volatile 只能修饰成员变量,不能修饰局部变量
  • volatile 可以禁止指令重排

二、内存屏障

  我们知道了volatile关键字其中一个功能可以禁止指令重排序,那么它是怎么实现的呢,这里引出一个内存屏障的概念:

  概念:内存屏障,也称内存栅栏,内存栅障,屏障指令等, 是一类同步屏障指令,是CPU或编译器在对内存随机访问的操作中的一个同步点,使得此点之前的所有读写操作都执行后才可以开始执行此点之后的操作

  作用:

  • 保证特定操作的顺序
  • 保证某些变量的内存可见性(利用该特性实现volatile的内存可见性)

种类:

三、volatile关键字在程序运行中的表现

  为了实现volatile的内存语义,编译器在生成字节码时,会在指令序列中插入内存屏障来禁止特定类型的处理器重排序。

  对于编译器来说,发现一个最优方案来最小化插入屏障的总数几乎是不可能的。

  所以JMM采取保守策略来优先保证正确性,然后再去追求执行效率。下面是基于保守策略的JMM内存屏障插入策略:

  • 在每个volatile写操作前面插入一个StoreStore屏障
  • 在每个volatile写操作后面插入一个StoreLoad屏障
  • 在每个volatile读操作后面插入一个LoadLoad屏障
  • 在每个volatile读操作后面插入一个LoadStore屏障

四、volatile和synchronized的区别

  • volatile本质是在告诉jvm当前变量在寄存器(工作内存)中的值是不确定的,需要从主存中读取; synchronized则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住。
  • volatile仅能使用在变量级别;synchronized则可以使用在变量、方法、和类级别的。
  • volatile仅能实现变量的修改可见性,有序性,但不能保证原子性;而synchronized则可以保证变量的修改可见性和原子性,但不能保证有序性。
  • volatile不会造成线程的阻塞;synchronized可能会造成线程的阻塞。
  • volatile标记的变量不会被编译器优化【不会被重排序】;synchronized标记的变量可以被编译器优化【可以被重排序】。

五、小问答

Q:volatile为啥不能保证原子性?

  首先需要了解的是,Java中只有对基本类型变量的赋值和读取是原子操作,如 i  = 1 的赋值操作,但是像 j =  i 或者 i++ 这样的操作都不是原子操作,因为他们都进行了多次原子操作,比如先读取i的值,再将i的值赋值给j,两个原子操作加起来就不是原子操作了。

  还有一个原因,指令和变量读取到了寄存器中,volatile的缓存一致性MESI【下一篇会解释~】可以保证实时更新内存或者3级缓存中的值,但是没法去通知寄存器更新,所以导致寄存器还是用的旧数据进行运算。

  所以,如果一个变量被volatile修饰了,那么肯定可以保证每次读取这个变量值的时候得到的值是最新的,但是一旦需要对变量进行自增这样的非原子操作,就不能保证这个操作的原子性了。

  PS:单个缓存行的锁定是可以保证缓存行内数据原子性的

Q:JMM的保守策略是啥?

  思路:在每个 volatile 写的后面,或者在每个 volatile 读的前面插入一个 StoreLoad 屏障。

  保守策略:在每个 volatile 写的后面插入一个 StoreLoad 屏障

  原因:volatile 写/读内存语义的常见场景是:一个写线程写 volatile 变量,多个读线程读同一个 volatile 变量【一写多读】。当读线程的数量大大超过写线程时,选择在volatile写之后插入 StoreLoad 屏障将带来可观的执行效率的提升。

Q:volatile有啥用?

  • 现象上:可以解决可见性问题,轻量级同步机制【轻量级:不会阻塞线程】
  • 字节码上:会多加一个标记ACC_VOLATILE

  PS:加了volatile可以保证线程间可以及时看到变量的变更,不加的话也可能可以看到变量的变更,但是不能保证及时看到。

 

 

 

参考文章:

posted @ 2020-07-17 17:35  有梦想的肥宅  阅读(323)  评论(0编辑  收藏  举报