【并发编程】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.mzph.cn/news/652958.shtml

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

相关文章

响应式Web开发项目教程(HTML5+CSS3+Bootstrap)第2版 例5-2 JavaScript 获取HTML元素对象

代码 <!doctype html> <html> <head> <meta charset"utf-8"> <title>JavaScript 获取 HTML 元素对象</title> </head><body> <input type"text" value"admin" /> <br> <input …

【深度学习:t-SNE 】T 分布随机邻域嵌入

【深度学习&#xff1a;t-SNE 】T 分布随机邻域嵌入 降低数据维度的目标什么是PCA和t-SNE&#xff0c;两者有什么区别或相似之处&#xff1f;主成分分析&#xff08;PCA&#xff09;t-分布式随机邻域嵌入&#xff08;t-SNE&#xff09; 在 MNIST 数据集上实现 PCA 和 t-SNE结论…

数据中心代理IP:最优性价比业务应用指南

数据中心代理IP在应对高速高并发的业务时&#xff0c;以独特的高速传输&#xff0c;游刃有余地应对多任务处理&#xff0c;适合于特定业务场景的高效加速。理性选用数据中心代理IP&#xff0c;可以为业务将迎来更加稳健和迅速的发展。今天&#xff0c;我们将揭示数据中心代理IP…

Python代码耗时统计

time模块 在代码执行前后各记录一个时间点&#xff0c;两个时间戳相减即程序运行耗时。这种方式虽然简单&#xff0c;但使用起来比较麻烦。 time.time() 函数返回的时间是相对于1970年1月1日的秒数 import timestart time.time() time.sleep(1) end time.time() print(f&…

flutter 搜索框实现,键盘搜索按钮,清空,防抖

import package:flutter/material.dart; import package:flutter_screenutil/flutter_screenutil.dart; import package:flutter_svg/svg.dart; import package:sy_project/config/app_colors.dart; import package:sy_project/core/assets.dart;/// 搜索textview class Custom…

JavaSE核心基础-面向对象一 - 类和对象 成员变量与局部变量-知识点

1.面向对象的特点 ①.面向对象是一种常见的思想&#xff0c;比较符合人们的思考习惯&#xff1b; ②.面向对象可以将复杂的业务逻辑简单化&#xff0c;增强代码复用性&#xff1b; ③.面向对象具有抽象、封装、继承、多态等特性。 2.面向对象的三大特征 ①.封装性 封装是面向对…

【MIdjourne基础】 |MIdjourney基础参数全解析,各类辅助知识

文章目录 1 参数列表1.1 基础参数列表 2 基础参数详解2.1 模型版本选择2.2 模型出图模式选择2.3 基础生图参数2.3.1 --ar2.3.2 --stylize2.3.3 --no2.3.4 --chaos2.3.5 --quality2.3.6 --stop2.3.7 --hd2.3.8 --repeat 1 参数列表 1.1 基础参数列表 模型版本选择 目标参数作…

Linux delay相关函数实现

1、udelay 与 sleep 相关函数相比&#xff0c;delay 的最大区别是忙等、一直占用 CPU&#xff0c;而 sleep 会让出 CPU 控制权。 mdelay、ndelay都是基于 udelay 来实现的。在 include/linux/delay.h 中&#xff0c;如下&#xff1a; /** Using udelay() for intervals grea…

杂项基础知识

换行与回车 ASCII中的CR与LF CR&#xff08;Carriage Return&#xff09;&#xff0c;回车字符\r&#xff0c;控制字符&#xff0c;将光标移动到本行行首 LF&#xff08;Line Feed&#xff09;&#xff0c;换行字符\n&#xff0c;控制字符&#xff0c;将光标下移一行 ASCII…

开源项目对于新用户和初学者适合哪些工作

目录 一、阅读和理解文档 二、报告问题 三、测试和验证修复 四、编写和更新文档 五、简单的代码更改和修复 六、参与社区讨论 开源项目对于新用户和初学者来说&#xff0c;提供了宝贵的学习和实践机会。以下是一些适合新用户和初学者参与的工作&#xff1a; 一、阅读和理…

【2024美赛实战】预测模型:灰色预测模型GM(1,1)

当题目数据少且无明显规律的时候&#xff0c;且要求进行短期预测的时候&#xff0c;或许可以考虑另一种预测方法——灰色预测模型GM(1,1)&#xff0c;虽然是个比较基础的预测模型&#xff0c;但在美赛O奖论文中登场次数也是比较多的。 一 预测问题的一般步骤 二 灰色预测模型…

Redis客户端之Redisson(三)Redisson分布式锁

一、背景&#xff1a; 高效的分布式锁设计应该包含以下几个要点&#xff1a; 1、互斥&#xff1a; 在分布式高并发的条件下&#xff0c;我们最需要保证&#xff0c;同一时刻只能有一个线程获得锁&#xff0c;这是最基本的一点 2、防止死锁&#xff1a; 在分布式高并发的条…

Cesium材质特效

文章目录 0.引言1.视频材质2.分辨率尺度3.云4.雾5.动态水面6.雷达扫描7.流动线8.电子围栏9.粒子烟花10.粒子火焰11.粒子天气 0.引言 现有的gis开发方向较流行的是webgis开发&#xff0c;其中Cesium是一款开源的WebGIS库&#xff0c;主要用于实时地球和空间数据的可视化和分析。…

STM32 SDIO接口配置与使用方法详解

STM32的SDIO&#xff08;Secure Digital Input Output&#xff09;接口是一种用于SD卡和MMC卡的高速数据传输接口&#xff0c;通过SDIO接口可以实现对SD卡和MMC卡的读写操作。在本文中&#xff0c;我们将详细介绍STM32 SDIO接口的配置和使用方法&#xff0c;并附上相关的代码示…

动态规划算法题刷题笔记

首先看动态规划的三要素&#xff1a;重叠子问题、最优子结构和状态转移方程。 重叠子问题&#xff1a;存在大量的重复计算 最优子结构&#xff1a; 状态转移方程&#xff1a;当前状态转移成以前的状态 动态规划的解题步骤主要有&#xff1a; 确定 dp 数组以及下标的含义状…

苍穹外卖-前端部分(持续更新中)

d 第二种&#xff1a;cmd中输入 vue ui进入图形化界面选择npm,vue2进行创建 先将创建的Vue框架导入Vsocde开发工具 然后ctrshiftp 输入npm 点击serve将项目启动 下这种写法跨域会报错&#xff1a; 解决方法&#xff1a; \ 注意 这种用法&#xff1a;&#xff08;不是单引号…

Android Handler完全解读

一&#xff0c;概述 Handler在Android中比较基础&#xff0c;本文笔者将对此机制做一个完全解读。读者可简单参考上述类图与时序图&#xff0c;便于后续理解。 二&#xff0c;源码解读 1&#xff0c;主线程伊始 众所周知&#xff0c;通过Zygote的fork方式&#xff0c;新创建…

远程方法调用Remote Method Invocation

网络编程包含不同的编程范式&#xff0c;主要有以下两种&#xff1a; 1. 套接字编程&#xff08;Sockets Programming&#xff09; 这种方式首先需要设计一个协议。协议是指在网络通信中&#xff0c;客户端和服务器如何进行数据交换的规则和标准。 在确定了协议之后&a…

SSH客户端 Termius for Mac 中文激活版

Termius for Mac是一款强大的终端和SSH客户端&#xff0c;为开发人员、系统管理员和网络工程师提供了全面的远程访问和管理工具。 软件下载&#xff1a;Termius for Mac 中文激活版下载 无论您是在使用Mac、Windows还是Linux系统&#xff0c;Termius都能提供出色的功能和用户体…

静态代理IP该如何助力Facebook多账号注册运营?

在Facebook运营中&#xff0c;充分利用静态代理IP是多账号运营的关键一环。通过合理运用静态代理IP&#xff0c;不仅可以提高账号安全性&#xff0c;还能有效应对Facebook的算法和限制。以下是这些关键点&#xff0c;可以帮助你了解如何运用静态代理IP进行Facebook多账号运营&a…