当前位置: 首页 > news >正文

【多线程】volatile 关键字

volatile 关键字

  • 1. 保证内存可见性
  • 2. 禁止指令重排序
  • 3. 不保证原子性

1. 保证内存可见性

内存可见性问题: 一个线程针对一个变量进行读取操作,另一个线程针对这个变量进行修改操作,
此时读到的值,不一定是修改后的值,即这个读线程没有感知到变量的变化。

volatile 修饰的变量, 能够保证 “内存可见性”.
代码在写入 volatile 修饰的变量的时候,

  • 改变线程工作内存(寄存器)中volatile变量副本的值
  • 将改变后的副本的值从工作内存刷新到主内存(内存)

代码在读取 volatile 修饰的变量的时候

  • 从主内存(内存)中读取volatile变量的最新值到线程的工作内存(寄存器)中
  • 从工作内存(寄存器)中读取volatile变量的副本

加上 volatile , 强制读写内存. 速度是慢了, 但是数据变的更准确了.

代码示例:

class SynchronizedBlockExample {static class Counter {
//        public volatile int flag = 0;public int flag = 0;}public static void main(String[] args) {Counter counter = new Counter();Thread t1 = new Thread(() -> {while (counter.flag == 0) {// do nothing}System.out.println("循环结束!");});Thread t2 = new Thread(() -> {Scanner scanner = new Scanner(System.in);System.out.println("输入一个整数:");counter.flag = scanner.nextInt();});t1.start();t2.start();}
}

while 循环里面 counter.flag == 0, 里面有 两步操作,并且线程启动之后一直在快速循环判断:

  • load,把内存中 flag 的值读到寄存器里面
  • cmp,把寄存器里面的值和 0 比较大小

上述循环执行非常快,1s 执行百万次以上。
但是循环这么多次,在 t2 修改之前, load 很多次,发现值都一样
而 load 相对于 cmp 慢很多,load 是从内存中读取数据,cmp 是在寄存器里面进行比较,再加上多次 load 的结果都一样,
所以编译器就做了一个大胆的想法,不再真正的 load 了,既然都一样,好像没人改,那就不从 内存中读取了,直接从 寄存器里面读取。
所以当我们输入一个非零值时,由于编译器还是从寄存器里面读取,而不是读内存,所以线程 t1 无法立即感知到。

当你输入一个 非 0 值时,如果不加上 volatile , 线程 t1 无法及时感知到;
加上 volatile,线程 t1 被强制读取内存,能立马感知到 flag 被赋予 非 0 值,会立即退出循环。
归根结底:还是编译器进行优化是发生了误判,但是这个什么时候优化又比较 “玄学”, 可能多加上一行代码可能就不优化了,所以稳妥的方法还是该加 volatile 的地方都加上。

2. 禁止指令重排序

什么是代码重排序?
举个栗子:
一段代码是这样的:

1. 去前台取下 U2. 去教室写 10 分钟作业
3. 去前台取下快递

为了提高效率, JVM、CPU指令集会对其进行优化,比如,按 1->3->2的方式执行,也是没问题,可以少跑一次前台,提高效率。这种就叫做指令重排序。

编译器对于指令重排序的前提是 “保持逻辑不发生变化”.
这一点在单线程环境下比较容易判断, 但是在多线程环境下就没那么容易了,
多线程的代码执行复杂程度更高, 编译器很难在编译阶段对代码的执行效果进行预测,
因此激进的重排序很容易导致优化后的逻辑和之前不等价.

代码举例: 线程安全的单例模式:

class Singleton {// 使用 volatile 防止指令重排序private static volatile Singleton instance = null; // 使用 static, 该实例就是该类的唯一实例// 私有化构造方法, 防止在类外创造实例private Singleton(){}public static Singleton getSingleton() {// 判断是否需要加锁if (instance == null) {synchronized (Singleton.class) { // 针对类对象加锁, 保证所有线程都是针对同一个对象加锁// 判断是否需要创建实例if (instance == null) {instance = new Singleton();}}}return instance;}
}

对于上面这个代码, 就是一个单例模式, 只有一个 Singleton 实例, 并且只能通过 getSingleton 方法获得。

获得实例的步骤是:

  1. 判断实例是否为空,以此来决定是否要加锁。
  2. 因为 代码块里面是 先判断是否为空,再创建对象,这是两个步骤,所以要加锁。
  3. 实例为 null, 就创建实例。
  4. 返回实例。

其中创建实例 new Singleton() 又分为 三个步骤:

  1. 分配内存空间
  2. 对内存空间进行初始化
  3. 把内存空间的地址赋给引用

假如没有使用 volatile 关键字,编译器可能对此进行了优化,进行了指令重排序,那么有可能优化为 1 -> 3 -> 2 。

这样的话,当第一个线程 t1 要获取实例时,因为实例为null, 所以肯定会创建实例,但是可能编译器进行了优化,那么可能顺序就变成了 1 -> 3 -> 2

  • 先开辟了一块空间
  • 将空间地址赋值给引用
  • 对空间初始化

当进行完第二步,把空间地址赋值给引用后,还没来得及初始化,此时另外一个线程 t2 来获取实例了, 进行判断时,发现 instance 不为空,那么就直接返回实例了
在这里插入图片描述

t2 拿到实例后,直接进行使用,那么就会报错了,因为虽然开辟了空间,但是 t1 还没来得及对空间进行初始化,所以访问的是非法的地址。

解决:
对 instance 对象加上 volatile 关键字,禁止指令重排序。

3. 不保证原子性

volatile 和 synchronized 有着本质的区别. synchronized 能够保证原子性和内存可见性; volatile 保证的是内存可见性以及禁止指令重排序。

class SynchronizedBlockExample {static class Counter {volatile public int count = 0;void increase() {count++;}}public static void main(String[] args) throws InterruptedException {final Counter counter = new Counter();Thread t1 = new Thread(() -> {for (int i = 0; i < 50000; i++) {counter.increase();}});Thread t2 = new Thread(() -> {for (int i = 0; i < 50000; i++) {counter.increase();}});t1.start();t2.start();t1.join();t2.join();System.out.println(counter.count);}
}

给 count 加上 volatile 关键字,最终 count 的值仍然无法保证是 100000。

但是使用 synchronized 的话就能保证原子性。使得代码执行结果符合预期。

class SynchronizedBlockExample {static class Counter {public int count = 0;synchronized void increase() {count++;}}public static void main(String[] args) throws InterruptedException {final Counter counter = new Counter();Thread t1 = new Thread(() -> {for (int i = 0; i < 50000; i++) {counter.increase();}});Thread t2 = new Thread(() -> {for (int i = 0; i < 50000; i++) {counter.increase();}});t1.start();t2.start();t1.join();t2.join();System.out.println(counter.count);}
}

好啦! 以上就是对 volatile 的讲解,希望能帮到你 !
评论区欢迎指正 !

相关文章:

【多线程】volatile 关键字

volatile 关键字 1. 保证内存可见性2. 禁止指令重排序3. 不保证原子性 1. 保证内存可见性 内存可见性问题: 一个线程针对一个变量进行读取操作&#xff0c;另一个线程针对这个变量进行修改操作&#xff0c; 此时读到的值&#xff0c;不一定是修改后的值&#xff0c;即这个读线…...

【Windows注册表内容详解】

Windows注册表内容详解 第一章节 注册表基础 一、什么是注册表 注册表是windows操作系统、硬件设备以及客户应用程序得以正常运行和保存设置的核心“数据库”&#xff0c;也可以说是一个非常巨大的树状分层结构的数据库系统。 注册表记录了用户安装在计算机上的软件和每个程…...

大数据Hadoop入门之集群的搭建

hadoop的三种运行模式 本地模式:测试本地的hadoop是否能够运行&#xff0c;用来运行官方的代码。伪分布模式:原先有人拿来测试&#xff0c;目前测试都不用这个模式了。完全分布模式&#xff1a;多台服务器组成分布式环境&#xff0c;生产环境使用 分布式主机文件同步命令 sc…...

华为云云耀云服务器L实例评测|基于云服务器的minio部署手册

华为云云耀云服务器L实例评测|基于云服务器的minio部署手册 【软件安装版本】【集群安装&#xff08;是&#xff09;&#xff08;否&#xff09;】 版本 创建人 修改人 创建时间 备注 1.0 jz jz 2023.9.2 minio华为云耀服务器 一. 部署规划与架…...

龙智携手Atlassian和JFrog举办线下研讨会,探讨如何提升企业级开发效率与质量

2023年9月8日&#xff0c;龙智将携手Atlassian和JFrog于上海举办线下研讨会&#xff0c;以“大规模开发创新&#xff1a;如何提升企业级开发效率与质量”为主题&#xff0c;邀请龙智高级咨询顾问、Atlassian认证专家叶燕秀&#xff0c;紫龙游戏上海研发中心高级项目管理主管叶凯…...

2023数学建模国赛A题定日镜场的优化设计- 全新思路及代码

背景资料关键信息和要点如下&#xff1a; 定日镜&#xff1a;塔式太阳能光热发电站的基本组件&#xff0c;由纵向转轴和水平转轴组成&#xff0c;用于反射太阳光。 定日镜场&#xff1a;由大量的定日镜组成的阵列。 集热器&#xff1a;位于吸收塔顶端&#xff0c;用于收集太…...

CSS笔记(黑马程序员pink老师前端)圆角边框

圆角边框 border-radius:length; 效果显示 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>Documen…...

水表电表集中远程抄表系统分析

电表水表远程抄表系统石家庄光大远通电气有限公司主要经营自动抄表,远程抄表,集中抄表,新供应信息&#xff0c;是石家庄光大远通电气有限公司自动远程抄表系统集信号采集、网络通信于一体的高性能抄表装置&#xff0c;该系统以485通讯方式读取水表电表的数据,以MBUS通讯方式读取…...

Android 通知

1. 原生Android通知的几种显示方式&#xff1a; 状态栏的图标&#xff1a;发出通知后&#xff0c;通知会先以图标的形式显示在状态栏中。 抽屉式通知栏&#xff1a;用户可以在状态栏向下滑动以打开抽屉式通知栏&#xff0c;并在其中查看更多详情及对通知执行操作。在应用或用户…...

【Unittest】Requests实现小程序项目接口测试

文章目录 一、搭建接口测试框架二、初始化日志三、定义全局变量四、封装接口五、编写测试用例六、生成测试报告 一、搭建接口测试框架 目录结构如下。 二、初始化日志 在utils.py文件中编写如下如下代码&#xff0c;初始化日志。 # 导入app.py全局变量文件 import app import l…...

Mac 搭建本地服务器

文章目录 一、启动服务器二、添加文件到本地服务三、手机/其他电脑 访问本机服务器 MacOS 自带Apatch 服务器。所以我这里选择Apatch服务器搭建 一、启动服务器 在safari中输入 http://127.0.0.1/ &#xff0c;如果页面出现 it works&#xff0c;则代表访问成功。启动服务器 …...

区块链基础之编写合约二

一、了解solidity中的关键字。 二、了解solidity中的类型。 三、编写合约 1.这里列出一些solidity中的关键字&#xff0c;有哪些。 pragma 作用&#xff1a;是告知编译器如何处理源代码的通用指令&#xff08;例如&#xff0c; pragma once &#xff09;。public 作用&#…...

【前端基础】js 如何判断一个值是数组

在JavaScript中&#xff0c;可使用不同的方法来判断一个值是否是一个数组。以下是一些常用的方法&#xff1a; 使用 Array.isArray() 方法&#xff1a; if (Array.isArray(value)) {// 值是一个数组 } else {// 值不是一个数组 }Array.isArray() 方法是最简单和推荐的方法&…...

Linux之NFS服务器

目录 Linux之NFS服务器 简介 NFS背景介绍 生产应用场景 NFS工作原理 NFS工作流程图 流程 NFS的安装 安装nfs服务 安装rpc服务 启动rpcbind服务同时设置开机自启动 启动nfs服务同时设置开机自启动 NFS的配置文件 主配置文件分析 示例 案例 --- 建立NFS服务器&#…...

ES delete_by_query条件删除的几种方式

es 查询删除的几种方式 1.根据id删除 #根据id删除 POST /indexname/_delete_by_query {"query": { "match": {"id": "100000"}} } 2.根据多个id删除 #根据多个id删除 POST /indexname/_delete_by_query {"query": {"…...

1.springboot 集成elasticsearch组件

1.前置条件已经安装和搭建好了elasticsearch中间件 一&#xff1a;项目中引入elasticsearch相关依赖 我安装的elasticsearch版本是7.10.2 对应依赖的版本保持一致 此处省略springboot 搭建及必要的依赖项 <dependency><groupId>org.elasticsearch.client</group…...

【学习笔记】元学习如何解决计算机视觉少样本学习的问题?

目录 1 计算机视觉少样本学习 2 元学习 3 寻找最优初始参数值方法&#xff1a;MAML 3.1 算法步骤 3.2 代码&#xff1a;使用MAML 和 FO-MAML、任务增强完成Few-shot Classification 4 距离度量方法&#xff1a;Siamese Network,ProtoNet,RN 4.1 孪生网络&#xff08;Sia…...

【C语言】17-函数-3

1. 链接属性 当组成一个程序的各个源文件分别被编译之后,所有的目标文件以及那些从一个或多个函数库中引用的函数将链接在一起,形成可执行程序。然而,如果相同的标识符出现在几个不同的源文件中时,它们是表示同一个实体,还是表示不同的实体?标识符的链接属性决定如何处理…...

人工智能:为你提供的未来工作岗位

随着科技的快速发展&#xff0c;人工智能(Artificial Intelligence,AI)正逐渐渗透到各个领域。本文探讨一下人工智能可以提供的工作岗位&#xff0c;以期帮助大家更好地了解这个新兴行业的就业前景。 文章目录 1 机器学习工程师2 数据科学家3 自然语言处理工程师4 机器视觉工程…...

HashMap、LinkedHashMap、ConcurrentHashMap、ArrayList、LinkedList的底层实现。

HashMap、LinkedHashMap、ConcurrentHashMap、ArrayList、LinkedList的底层实现。 HashMap相关问题 1、你用过HashMap吗&#xff1f;什么是HashMap&#xff1f;你为什么用到它&#xff1f;用过&#xff0c;HashMap是基于哈希表的Map接口的非同步实现&#xff0c; 它允许null键…...

云计算——弹性云计算器(ECS)

弹性云服务器&#xff1a;ECS 概述 云计算重构了ICT系统&#xff0c;云计算平台厂商推出使得厂家能够主要关注应用管理而非平台管理的云平台&#xff0c;包含如下主要概念。 ECS&#xff08;Elastic Cloud Server&#xff09;&#xff1a;即弹性云服务器&#xff0c;是云计算…...

黑马Mybatis

Mybatis 表现层&#xff1a;页面展示 业务层&#xff1a;逻辑处理 持久层&#xff1a;持久数据化保存 在这里插入图片描述 Mybatis快速入门 ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/6501c2109c4442118ceb6014725e48e4.png //logback.xml <?xml ver…...

从WWDC看苹果产品发展的规律

WWDC 是苹果公司一年一度面向全球开发者的盛会&#xff0c;其主题演讲展现了苹果在产品设计、技术路线、用户体验和生态系统构建上的核心理念与演进脉络。我们借助 ChatGPT Deep Research 工具&#xff0c;对过去十年 WWDC 主题演讲内容进行了系统化分析&#xff0c;形成了这份…...

Docker 运行 Kafka 带 SASL 认证教程

Docker 运行 Kafka 带 SASL 认证教程 Docker 运行 Kafka 带 SASL 认证教程一、说明二、环境准备三、编写 Docker Compose 和 jaas文件docker-compose.yml代码说明&#xff1a;server_jaas.conf 四、启动服务五、验证服务六、连接kafka服务七、总结 Docker 运行 Kafka 带 SASL 认…...

ESP32 I2S音频总线学习笔记(四): INMP441采集音频并实时播放

简介 前面两期文章我们介绍了I2S的读取和写入&#xff0c;一个是通过INMP441麦克风模块采集音频&#xff0c;一个是通过PCM5102A模块播放音频&#xff0c;那如果我们将两者结合起来&#xff0c;将麦克风采集到的音频通过PCM5102A播放&#xff0c;是不是就可以做一个扩音器了呢…...

如何为服务器生成TLS证书

TLS&#xff08;Transport Layer Security&#xff09;证书是确保网络通信安全的重要手段&#xff0c;它通过加密技术保护传输的数据不被窃听和篡改。在服务器上配置TLS证书&#xff0c;可以使用户通过HTTPS协议安全地访问您的网站。本文将详细介绍如何在服务器上生成一个TLS证…...

vue3+vite项目中使用.env文件环境变量方法

vue3vite项目中使用.env文件环境变量方法 .env文件作用命名规则常用的配置项示例使用方法注意事项在vite.config.js文件中读取环境变量方法 .env文件作用 .env 文件用于定义环境变量&#xff0c;这些变量可以在项目中通过 import.meta.env 进行访问。Vite 会自动加载这些环境变…...

Linux C语言网络编程详细入门教程:如何一步步实现TCP服务端与客户端通信

文章目录 Linux C语言网络编程详细入门教程&#xff1a;如何一步步实现TCP服务端与客户端通信前言一、网络通信基础概念二、服务端与客户端的完整流程图解三、每一步的详细讲解和代码示例1. 创建Socket&#xff08;服务端和客户端都要&#xff09;2. 绑定本地地址和端口&#x…...

STM32HAL库USART源代码解析及应用

STM32HAL库USART源代码解析 前言STM32CubeIDE配置串口USART和UART的选择使用模式参数设置GPIO配置DMA配置中断配置硬件流控制使能生成代码解析和使用方法串口初始化__UART_HandleTypeDef结构体浅析HAL库代码实际使用方法使用轮询方式发送使用轮询方式接收使用中断方式发送使用中…...

【网络安全】开源系统getshell漏洞挖掘

审计过程&#xff1a; 在入口文件admin/index.php中&#xff1a; 用户可以通过m,c,a等参数控制加载的文件和方法&#xff0c;在app/system/entrance.php中存在重点代码&#xff1a; 当M_TYPE system并且M_MODULE include时&#xff0c;会设置常量PATH_OWN_FILE为PATH_APP.M_T…...