【并发编程】volatile原理

 

       📝个人主页:五敷有你      
 🔥系列专栏:并发编程
⛺️稳重求进,晒太阳

volatile原理实现是内存屏障,Memory Barrier

  • 对volatile变量的写指令会加入写屏障。
  • 对volatile变量的读指令会加入读屏障

如何保证可见性

写屏障(sfence)保证在该屏障之前的,对共享变量的改动,都同步到主存当中

不只是把volatile修改同步到主存中,还会把之前的一些修改同步到主存中

如下,就是在同步ready的时候也会同步num

public void actor2(I_Result r) {num = 2;//之后也会同步到主存中ready = true; // ready 是 volatile 赋值带写屏障// 写屏障}

而读屏障(lfence)保证在该屏障之后,对共享变量的读取,加载的是主存中最新数据

如下,就是从主存中读取到的值ready

public void actor1(I_Result r) {// 读屏障// ready 是 volatile 读取值带读屏障if(ready) {r.r1 = num + num;} else {r.r1 = 1;}}

下图理解 

如何保证有序性

1.写屏障会确保指令重排序时,不会将写屏障之前的代码排在写屏障之后

如下:就是防止num的赋值操作走到ready=true的后面(防止前面的之前的语句没做,影响后续的结果)

public void actor2(I_Result r) {num = 2;ready = true; // ready 是 volatile 赋值带写屏障// 写屏障}

2.读屏障会确保指令重排序时,不会将读屏障之后的代码排在读屏障之前

如下:读屏障是防止后面的赋值操作走到前面(走到前面的话会有概率影响你的volatile修饰的变量)

public void actor1(I_Result r) {// 读屏障// ready 是 volatile 读取值带读屏障if(ready) {r.r1 = num + num;} else {r.r1 = 1;}}

不能解决指令交错

  • 写屏障仅仅是保证之后的读能够读到最新的结果,但不能保证读跑到它前面去(不懂)。t2线程啥时候读不能保证
  • 而有序性的保证也只是保证了本线程内相关代码不被重排序。至于两个线程之间谁前谁后,这是由CPU决定的

double-checked locking问题

synchronized是可以保证原子有序,可见的,但前提是,需要把共享变量都交给synchronized管理

下面的代码就有外部暴漏的风险

public final class Singleton {private Singleton() { }private static Singleton INSTANCE = null;public static Singleton getInstance() {if(INSTANCE == null) { // t2// 首次访问会同步,而之后的使用没有 synchronizedsynchronized(Singleton.class) {if (INSTANCE == null) { // t1INSTANCE = new Singleton();}}}return INSTANCE;}
}

以上的实现特点是:

  • 懒惰实例化
  • 首次使用 getInstance() 才使用 synchronized 加锁,后续使用时无需加锁
  • 有隐含的,但很关键的一点:第一个 if 使用了 INSTANCE 变量,是在同步块之外

但在多线程环境下,上面的代码是有问题的,getInstance 方法对应的字节码为:

其中

17 表示创建对象,将对象引用入栈 // new Singleton

20 表示复制一份对象引用 // 引用地址

21 表示利用一个对象引用,调用构造方法

24 表示利用一个对象引用,赋值给 static INSTANCE

也许 jvm 会优化为:先执行 24,再执行 21。如果两个线程 t1,t2 按如下时间序列执行:

        关键在于 0: getstatic 这行代码在 monitor 控制之外,它就像之前举例中不守规则的人,可以越过 monitor 读取INSTANCE 变量的值

        这时 t1 还未完全将构造方法执行完毕,如果在构造方法中要执行很多初始化操作,那么 t2 拿到的是将是一个未初始化完毕的单例

        对 INSTANCE 使用 volatile 修饰即可,可以禁用指令重排(防止synchronized的部分指令跑到instance的前面去),但要注意在 JDK 5 以上的版本的 volatile 才会真正有效

double-checked locking 解决

public final class Singleton {private Singleton() { }private static volatile Singleton INSTANCE = null;public static Singleton getInstance() {// 实例没创建,才会进入内部的 synchronized代码块if (INSTANCE == null) {synchronized (Singleton.class) { // t2// 也许有其它线程已经创建实例,所以再判断一次if (INSTANCE == null) { // t1INSTANCE = new Singleton();}}}return INSTANCE;}
}

字节码上看不出来 volatile 指令的效果

// -------------------------------------> 加入对 INSTANCE 变量的读屏障

0: getstatic #2               // Field INSTANCE:Lcn/itcast/n5/Singleton;

3: ifnonnull 37

6: ldc #3 // class cn/itcast/n5/Singleton

8: dup

9: astore_0

10: monitorenter -----------------------> 保证原子性、可见性

11: getstatic #2 // Field INSTANCE:Lcn/itcast/n5/Singleton;

14: ifnonnull 27

17: new #3 // class cn/itcast/n5/Singleton

20: dup

21: invokespecial #4 // Method "":()V

24: putstatic #2 // Field INSTANCE:Lcn/itcast/n5/Singleton;

// -------------------------------------> 加入对 INSTANCE 变量的写屏障

27: aload_0

28: monitorexit ------------------------> 保证原子性、可见性

29: goto 37

32: astore_1

33: aload_0

34: monitorexit

35: aload_1

36: athrow

37: getstatic #2 // Field INSTANCE:Lcn/itcast/n5/Singleton;

        如上面的注释内容所示,读写 volatile 变量时会加入内存屏障(Memory Barrier(Memory Fence)),保证下面两点:

  • 可见性:
    • 写屏障(sfence)保证在该屏障之前的 t1 对共享变量的改动,都同步到主存当中
    • 而读屏障(lfence)保证在该屏障之后 t2 对共享变量的读取,加载的是主存中最新数据
  • 有序性
    • 写屏障会确保指令重排序时,不会将写屏障之前的代码排在写屏障之后
    • 读屏障会确保指令重排序时,不会将读屏障之后的代码排在读屏障之前
  • 更底层是读写变量时使用 lock 指令来多核 CPU 之间的可见性与有序性

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/438718.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Java实现对系统CPU、内存占用率的控制

背景:由于使用的业主的云资源,由于使用率低,会不持续的缩减服务器配置。为了避免后续由于新业务上线,需要更多资源的时候,无法再次获得资源(回收容易,申请难)。 问题:怎…

第十八回 林冲水寨大并火 晁盖梁山小夺泊-FreeBSD/Ubunut使用ssh的scp传输文件

何涛在得到知府命令后,带领官兵出发前往石碣村捉拿强盗。在接近石碣村时,他们遇到了一些打渔的人,得知阮小五、阮小七两兄弟在湖中居住,非乘船不能到达。何涛决定所有人都下马,一起乘船前往湖中寻找阮家兄弟。 在行船…

Android 13以上版本读写SD卡权限适配

如题&#xff0c;最近工作上处理的问题&#xff0c;把解决方案简单逻列出来&#xff0c;供有需要的朋友参考之 解决方案&#xff1a; 1、配置权限 <uses-permission android:name"android.permission.READ_MEDIA_IMAGES" /><uses-permission android:name&q…

【LIBS】交叉编译TCPDUMP

目录 1. 安装编译工具2. 设置环境变量3. 编译libpcap3.1 安装依赖3.2 交叉编译 4. 编译TCPDUMP4.1 克隆仓库与生成构建环境4.2 静态链接LIBPCAP4.3 动态链接LIBPCAP4.4 构建与安装 5. 查看交叉编译结果5.1 文件布局 1. 安装编译工具 sudo apt-get install -y autoconf automak…

幻兽帕鲁服务器价格,这个价格不够电费的

幻兽帕鲁服务器价格多少钱&#xff1f;4核16G服务器Palworld官方推荐配置&#xff0c;阿里云4核16G服务器32元1个月、96元3个月&#xff0c;腾讯云换手帕服务器服务器4核16G14M带宽66元一个月、277元3个月&#xff0c;8核32G22M配置115元1个月、345元3个月&#xff0c;16核64G3…

某金属加工公司的核心人才激励体系搭建项目纪实

某大型金属加工企业位于河北地区&#xff0c;成立于2000年&#xff0c;隶属于某大型有色金属集团&#xff0c;是一家集科研、开发、生产、销售于一体的国有企业&#xff0c;人员达到1000人。经过多年发展、引入国际化设备&#xff0c;公司目前的产品具有高端化、前沿化的特点&a…

2024跨境电商独立站的优势有哪些?

随着全球化的发展&#xff0c;跨境电商正成为越来越多企业的发展战略。在跨境电商中&#xff0c;拥有独立站点的企业相比于仅在第三方平台上销售的企业&#xff0c;具有诸多优势。以下是跨境电商独立站的一些明显优势&#xff1a; 品牌塑造与建设&#xff1a; 独立站允许企业自…

107基于51单片机的数字温度计设计[proteus仿真]

基于51单片机的自行车测速系统设计[proteus仿真] 温度检测系统这个题目算是课程设计和毕业设计中常见的题目了&#xff0c;本期是一个基于51单片机的数字温度计设计 需要的源文件和程序的小伙伴可以关注公众号【阿目分享嵌入式】&#xff0c;赞赏任意文章 2&#xffe5;&…

Vue学习之nodejs环境搭建中的坑

Vue学习之nodejs环境搭建中的坑 1.nodejs安装后环境变量配置 &#xff08;1&#xff09;在nodejs安装目录下已有node_cache、node_global&#xff0c;如下&#xff1a; &#xff08;2&#xff09;在系统属性->环境变量中新建一个名为NODE_PATH的系统变量&#xff0c;值为n…

基于ncurse的floppy_bird小游戏

1. 需求分析 将运动分解为鸟的垂直运动和杆的左右运动。 2. 概要设计 2.1 鸟运动部分 2.2 杆的运动 3. 代码实现 #include <stdio.h> #include <ncurses.h>#include <stdlib.h> #include <time.h>int vx 0; int vy 1;int bird_r; int bird_c;int…

【计网·湖科大·思科】实验五 IPV4地址-分类地址和构建超网

&#x1f57a;作者&#xff1a; 主页 我的专栏C语言从0到1探秘C数据结构从0到1探秘Linux &#x1f618;欢迎关注&#xff1a;&#x1f44d;点赞&#x1f64c;收藏✍️留言 &#x1f3c7;码字不易&#xff0c;你的&#x1f44d;点赞&#x1f64c;收藏❤️关注对我真的很重要&…

【网络】传输层TCP协议 | 三次握手 | 四次挥手

目录 一、概述 2.1 运输层的作用引出 2.2 传输控制协议TCP 简介 2.3 TCP最主要的特点 2.4 TCP连接 二、TCP报文段的首部格式 三、TCP的运输连接管理 3.1 TCP的连接建立(三次握手) 3.2 为什么是三次握手&#xff1f; 3.3 为何两次握手不可以呢&#xff1f; 3.4 TCP的…