volatile特性和内存语义

volatile内存语义、原理详解、内存屏障 volatile 阅读详情

在多线程并发编程中,synchronized和volatile都扮演着重要的角色,volatile是轻量级的synchronized,它在多线程开发中保证了共享变量的可见性。

volatile特性

volatile变量自身有两个特性:

  1. 原子性:对于任意单个volatile变量的读/写具有原子性,但是类似与volatileVal++这种复合操作来说,它就不具有原子性。

  2. 可见性:对于一个volatile变量的读,总是能看到任意线程对这个volatile变量最后的写入。

原子性:这里强调的是对单个volatile变量的读或写具有原子性,这里的原子性其实是针对64位数据变量来说的,比如long和double。在一些32位的处理器上,如果要求对64位数据的写操作具有原子性,会带来比较大的开销。所以,为了照顾这种处理器,java语言规范鼓励但是不强制JVM对64位long型变量和double变量的写操作具有原子性。(大家注意,这里只是说写操作可以不具有原子性。因为从JDk5开始,java就要求任意的读操作都要具有原子性。)对于64位的写操作来说,它可以分为两个32位的总线读事务,那么,就会发生下面的情况:

    处理器A                        处理器B

(1)写long型变量的高32位
                            (2)读取整个long型变量
(3)写long型变量的低32位                         

从这里我们可以看出来,处理器B读取到的long变量其实是一个脏数据。如果我们用volatile来修饰这个变量,就会使它的写操作也具有原子性,就可以避免64位数据出现这种读写问题。

但是对于i++这种复合操作来说,volatile就显得无能为力了。i++其实分为三步:(这里的tmp其实是个抽象的概念)

  1. tmp = i;
  2. tmp = tmp + 1;
  3. i = tmp;

我们看一下造成i++结果异常的一种情况:

        线程A                         线程B

      tmp = i;
                                    tmp = i;
      tmp = tmp + 1;
      i = tmp;
                                    tmp = tmp + 1;
                                    i = tmp;

这里假设A和B都同时执行i++操作,i初始值为0,那么两个i++操作过后,i的值为1而不是2.

当然,这里的值也可能为2,只要排序的顺序有变化的话。我们不能确定两个线程并发得执行i++操作后能够得到一个确切的结果,这就构成了一个线程安全问题。

可见性:大家都知道,cup的速度比内存数据读取的速度快的不只是一个数量级,如果每次都要从内存去读取数据的话,就会造成cpu资源的严重浪费,为了消除这种极大的不平衡,就出现了缓冲区这种东西。先把数据读取到缓冲区,cpu直接从缓冲区拿数据,然后在写回缓冲区,在适当的时间将缓冲区中的数据写回内存。

JMM定义了线程和主内存之间的抽象关系:线程中的共享变量存储在主内存中,每个线程都有一个私有的本地内存(是一个抽象概念,涵盖缓存、写缓冲、寄存器等),本地内存存储了共享变量的副本,线程直接对本地内存中的共享变量进行读写,并在适当的时候将共享变量写会主内存。这个适当的时候其实是不确定的,这就造成了数据的不一致性。比如说线程A对本地内存中的变量执行了写操作,但是她还没将本地内存中的副本刷回主内存,这时候线程B去主内存中读取这个变量的值的时候,就不能读取到最新的值,也就是说读取到了脏数据。

当我们把共享变量声明为volatile后,每当有线程写这个变量时,都会及时将本地内存中的值刷回主内存,然后将其他线程中对这个变量的缓存值设置为无效,当任意线程再去读取这个变量的时候,只能去主内存中读取这个变量,这样就保证了写操作对任意线程的可见性。


volatile内存语义

volatile内存语义有以下两点:

  1. 当对一个volatile变量进行写操作的时候,JMM会把该线程对应的本地内存中的共享变量的值刷新到主内存中。
  2. 当读一个volatile变量的时候,JMM会把该线程对应的本地内存设置为无效,要求线程从主内存中读取数据。

注意:这里并不是说把volatile变量刷新回主内存,而是说把所有共享变量(包括volatile变量)刷新回主内存

也就是说,当对volatile进行写操作之后,接下来任意线程对该volatile变量进行读操作的时候,都能看见volatile变量以及volatile变量之前语句(在程序中写在volatile写语句前面的语句)对其他共享变量的写。我们来看下面的例子:

           线程A                         线程B

(1)      a = 1;
(2)     volatileVal = 0;
                                  (3) b = volatileVal;
                                  (4) c = a;

这里的volatile内存语义保证了在(3)执行之前,(1)(2)所产生的影响已经刷新回主内存,而且线程对所有共享变量的读取都要去主内存中读,这就保证了b = 1; c = 0;

volatile重排序

概括为以下三点(这三点之外的允许重排序):

  1. 当第二个操作为volatile写操做时,不管第一个操作是什么(普通读写或者volatile读写),都不能进行重排序.这个规则确保volatile写之前的所有操作都不会被重排序到volatile之后;
  2. 当第一个操作为volatile读操作时,不管第二个操作是什么,都不能进行重排序.这个规则确保volatile读之后的所有操作都不会被重排序到volatile之前;
  3. 当第一个操作是volatile写操作时,第二个操作是volatile读操作,不能进行重排序.这个规则和前面两个规则一起构成了:两个volatile变量操作不能够进行重排序

除开这三点之外的情况,可以进行重排序:

  1. 第一个操作是普通变量读/写,第二个是volatile变量的读
  2. 第一个操作是volatile变量的写,第二个是普通变量的读/写

慎用volatile

一些编程大牛往往会告诫我们说,尽量不要去使用volatile,因为使用volatile稍有不慎就会出现问题。如果严格遵循 volatile 的使用条件 —— 即变量真正独立于其他变量和自己以前的值 —— 在某些情况下可以使用 volatile 代替 synchronized 来简化代码。然而,使用 volatile 的代码往往比使用锁的代码更加容易出错。

推荐大家看一下这位大牛写的关于volatile正确使用的几种模式

并发编程系列之volatile内存语义 前言 前面介绍顺序一致性模型时,我们提到了程序如果正确的同步就会具备顺序一致性,这里所说的同步泛指广义上的同步,其中包括就包括同步原语volatile,那么volatile声明的变量为什么就能保证同步呢?这又是如何实现的呢?今天就让我们一起来揭开这神秘的面纱,OK,开始我们今天的并发编程之旅吧。 volatile的特性 volatile特性可以理解为对volatile变量的单个读写,看成是使用了同一个锁对这些单个读写进行了同步,他们之间的执行效果是一样的;volatile变量自身具有下面2种特性: 阅读详情

相关推荐

Star CCM+ 202206 (17.04) 详细安装步骤

StarCCM+ 17.04 安装包和详细安装步骤

薛定谔的alpha狗 3万+

[实验]-从汇编代码来看volatile关键字的作用

环境aarch64的交叉编译器 我们将C语言翻译成汇编代码,来看volatile关键字的作用 我们先看一段C语言代码 void udelay(uint64_t usec) { uint64_t start, cnt, delta,freq; volatile uint64_t delta_us = 0; //------------------在这里定义了一个volatile变量 uint64_t reference_ratio = 16;

baron-周贺贺-代码改变世界ctw 2544

银河麒麟服务器操作系统V10SP3 U盘安装中“基础软件仓库错误”的排查与修复指南

本文针对在无网络环境下使用U盘安装银河麒麟服务器操作系统V10SP3时,遇到的“基础软件仓库错误”问题,提供了详细的排查与修复指南。核心解决方案是通过进入救援模式,使用lsblk命令确定U盘实际设备路径,并在系统引导时手动添加inst.repo内核参数来指定安装源,从而绕过安装程序的自动探测故障,确保安装流程顺利进行。

ik67890123的博客 1193

volatile的内存语义

volatile的特性 volatile修饰的变量可以禁止指令重排序和保证了内存可见性和单一操作的原子性,类似i++这样的复合操作的原子性保证不了 有volatile关键字修饰的共享变量进行写操作数,会多出一个lock前缀指令。lock前缀指令其实就相当于一个内存屏障。在多处理器下,会将当前处理器工作内存的数据回写到主内存中,并且这个回写操作会其它线程中缓存该内存地址的数据无效。相当于会在写操作...

u010391342的博客 542

EFR32无线SoC开发指南:从环境搭建到性能优化

物联网边缘设备开发中,无线SoC芯片的选择与开发环境配置是关键第一步。EFR32作为Silicon Labs推出的多协议无线SoC系列,配合Simplicity Studio IDE,为开发者提供了完整的解决方案。本文以EFR32xG22开发套件为例,详细介绍开发环境搭建、工程创建、硬件配置等核心环节,特别针对RAILtest示例工程进行深入解析。通过实际案例演示如何配置无线参数、优化低功耗模式,并分享串口终端交互、吞吐量测试等实战技巧。对于开发者常见的问题如设备识别失败、无线通信异常等,提供了系统化的排查

weixin_30342363的博客 352

从零构建:ESP32S3无线DAP-Link调试器的硬件与软件协同设计

本文详细介绍了基于ESP32S3的无线DAP-Link调试器的硬件与软件协同设计,涵盖关键元器件选型、无线通信协议优化、双核任务处理及低功耗调试方案。通过ESP-NOW协议实现低延迟无线调试,结合USB协议栈与双核协同,为嵌入式开发提供高效灵活的调试解决方案。

gitlab7runner的博客 858

volatile类型变量

最近在代码里发现了一个叫Volatile类型的变量,竟然从来没见过,因此特搜索一番,以增加点见识。 volatile的作用: 作为指令关键字,确保本条指令不会因编译器的优化而省略,且要求每次直接读值. 简单地说就是防止编译器对代码进行优化.比如如下程序: XBYTE[2]=0x55; XBYTE[2]=0x56; XBYTE[2]=0x57; XBYTE[2]=

午夜游魂 403

CoppeliaSim和Python进行无人机联合仿真

CopeeliaSim和Python进行无人机轨迹跟踪联合仿真。首先建立起CoppeliaSim和Python的连接,其次在Python中生成轨迹,CoppeliaSim仿真环境中的无人机进行跟踪,并绘制出轨迹曲线,有每一步详细的教学。

加斯顿工程师的博客 987

C++中volatile的具体含义和可能的坑

提醒编译器不要对该变量相关的代码进行优化,避免出现意外的负面作用;对类似的表达式不进行编译层面的指令重排。编译指令重排也是一种编译器优化手段,这条严格来说也是第一条的变种。int g = 0;a < n;a++) {i++;//func对应的汇编a < n;a++) {j++;//func1 对应的汇编a < n;a++) {g++;//func2对应的汇编上面的代码中可以看到对于普通变量的++直接被编译器在编译期间就分析出来结果,直接赋值了,for循环直接被干掉了。

梦的灰色边沿... 3259

微信小程序 - 解决报错 operateAudio:fail audioInstance is not set(wx.getRecorderManager苹果ios真机调试运行,播放音频播放出错解决)

微信小程序,苹果ios系统,真机测试报错,播放音频报错operateAudio:fail audioInstance not exist,errCode: -1,小程序生成音频播放报错errMsg,真机调试报错开发者工具没问题,wx.getRecorderManager,音频实例,InnerAudioContext,各种方法都试了不行,只有ios苹果手机有这个问题,IOS 出现 operateAudio,在iOS无法播放的解决方案,更新到最新版本了还是不行,链接没有特殊字符,微信小程序全局音频设置,手机上播

🏆 前端领域优质创作者 1万+

SerDes学习笔记:DFE均衡架构示例(USB3.1、PCIe3.0\4.0)

本文探讨了DFE均衡器架构在不同数据接口中的应用特性。研究表明,抽头数量随数据速率提升而增加,1-16抽头架构较为常见。通过PCIeGen3/4实例分析,对比了单抽头、双抽头和15抽头DFE的性能差异,证明多抽头结构能通过精细调节有效提升噪声容限和降低误码率。文章详细阐述了DFE工作原理,包括其通过反馈信号对输入信号的补偿机制,以及不同抽头对消除码间干扰的作用。实验采用8Gbps速率(125ps/UI)进行波形仿真,直观展示了均衡处理前后的信号变化,为理解信道均衡技术提供了重要参考。

一只豌豆象的博客 517

volatile特性及内存语义

1.volatile变量自身具有下列特性:·可见性:对一个volatile变量的读,总是能看到(任意线程)对这个volatile变量最后的写入。·原子性:对任意单个volatile变量的读/写具有原子性,但类似于volatile++这种复合操作不具有原子性。 2.volatile写-读建立的happens-before关系 : 从内存语义的角度来说,volatile的写-读与锁的释放-获取有相...

weixin_34306676的博客 119

YOLOv11改进 | 主干/Backbone篇 | 轻量级移动端网络ShuffleNetV2(附代码+修改教程)

本文给大家带来的改进内容是ShuffleNetV2,这是一种为移动设备设计的高效CNN架构。其在ShuffleNetV1的基础上强调除了FLOPs之外,还应考虑速度、内存访问成本和平台特性。(我在YOLOv11n上修改该主干降低了GFLOPs,但是参数量还是有一定上涨,其非常适合轻量化的读者来使用,同时精度也有一定程度的上涨)。本文通过介绍其主要框架原理,然后教你如何添加该网络结构到网络模型中。

Snu77的博客 3386

【并发编程】二:volatile的特性以及内存语义及实现分析

目录前言特殊的变量Volatilevolatile的特性volatile变量的内存语义及实现案例分析结束语 前言 AbstractQueuedSynchronizer 应该算是JUC包里面最重要的一个类,也是最复杂的一个类。对它的理解决定这你对JUC编程的掌握程度。本篇文章开始就是要来啃JUC包下面这一块最难啃的骨头-AQS,但是在开始之前还是要来清理一下拦在我们面前的volatile关键字! 特殊的变量Volatile volatile的特性 可见性:对一个volatile变量的读,总是能看到(任意线

xiaomujiang_dafangzi的博客 276

怀旧版电脑GBC模拟器

再现原版GBC掌机的界面,电脑运行连游戏界面都一样大小,很怀旧,很真实,(英文版)强烈推荐,很小的文件,很难才找到的。

win10 跳过U8环境检测的IE WebControls.rar

win10 安装u8的时候 提示IE WebControls 不通过,这个工具可以欺骗u8的检查。 跳过U8环境检测的IE WebControls。提示导入失败后,重新检测通过。

并发编程(十八):volatile——特性、重排序规则与内存语义

volatile 的核心等价性:单个读 / 写操作等价于锁同步,复合操作无原子性重排序规则:volatile 写禁止前置重排序、volatile 读禁止后置重排序,通过内存屏障实现内存增强本质:通过约束底层优化,保障可见性和有序性,是轻量级并发安全工具锁的内存语义:覆盖 volatile 的所有特性,额外提供复合操作的原子性保障,是重量级但全量的并发解决方案。

2201_75940978的博客 395

STM32F407中文手册

STM32f407中文手册 本参考手册面向应用开发人员,提供有关使用 STM32F405xx/07xx、STM32F415xx/17xx、 STM32F42xxx 和 STM32F43xxx 微控制器存储器与外设的完整信息。STM32F405xx/07xx、STM32F415xx/17xx、STM32F42xxx 和 STM32F43xxx 构成一个微 控制器系列,各产品具有不同的存储器大小、封装和外设。有关订购信息以及器件的机械与电气特性,请参见数据手册。有关 ARM Cortex™-M4F 内核的信息,请参见《Cortex™-M4F 技术参考手册》。

并发编程(5)-volatile内存语义及JMM实现方式

volatile特性 1)可见性 对一个 volatile 变量的读总恩能够看到之前对这个变量的写 2)原子性 对任意单个volatile变量的读写具有原子性,但是对于复合操作 volatile++没有原子性。 疑问: 读为啥能有原子性? 因为缓存一致性协议如果读了一半被修改了,就重新读? 这里A线程写一个volatile变量后,B线程读同一个volatile变量。A线程在写volati...

zhangxm_qz的博客 503

温度PID控制PLC程序

温度PID稳定控制程序,适用于欧姆龙PLC,对其他PLC也具有很大参考价值

volatile内存语义

1、volatile特性 理解volatile特性的一个好方法是把对volatile变量的单个读/写,看成是使用同一个锁对这些单个读/写操作做了同步。下面通过具体的示例来说明,示例代码如下: class VolatileFeaturesExample { volatile long vl = 0L; // 使用volatile声明64位的long型变量 public void set(long l) { vl = l; // 单个volatile变量的写 }

赶路人儿 454

Java并发(四):volatile的内存语义

volatile可见性实验volatile的特性volatile 写与读的内存语义写语义读语义总结一下读写语义volatile的实现volatile的内存语义实现 前面我们已经讲过了volatile的作用、底层实现与内存屏障 下面就总结一下整个流程 volatile可见性实验 举个栗子 我这里开了两个线程,后面的线程去修改volatile变量,前面的线程不断获取volatile变量, 结果是会一致卡在死循环,控制台没有任何输出 假如将flag让volatile来进行修饰 结果是:三秒后,就不会不断打印.

GDUT_Trim的博客 1829

Java面试-volatile的内存语义

1、volatile的特性 理解volatile特性的一个好办法是把对volatile变量的单个读/写,看成是使用同一个锁对单个读/写操作做了同步。 ​ 代码示例: package com.lizba.p1; /** * <p> * volatile示例 * </p> * * @Author: Liziba * @Date: 2021/6/9 21:34 */ public class VolatileFeatureExample { /** 使用

Java框架、并发编程、分布式、微服务、Redis、HarmonyOS、中间件等技术 625

6、volatile与Java内存模型

1、被volatile修改的变量有2大特点1.1、特点可见性有序性 排序要求1.2、volatile的内存语义当写一个volatile变量时,JMM会把该线程对应的本地内存中的共享变量值立即刷新回主内存中。当读一个volatile变量时,JMM会把该线程对应的本地内存设置为无效,直接从主内存中读取共享变量所以volatile的写内存语义是直接刷新到主内存中,读的内存语义是直接从主内存中读取。2、内存屏障2.1、先说生活case没有管控,顺序难保设定规则,禁止乱序 :上海南京路步行街武警“人墙”当红灯2.2

赞的心路历程 562

Java内存模型之volatile(2)

前言 本小节主要介绍3个同步原语(synchronized、volatile和final)的内存语义及重排序规则在处理器中的实现;Java内存模型的设计,主要介绍Java内存模型的设计原理,及其与处理器内存模型和顺序一致性内存模型的关系。 volatile的内存语义 当声明共享变量为volatile后,对这个变量的读/写将会很特别。为了揭开volatile的神秘面 纱,下面将介绍volatile的......

7me 1万+
上一篇: happens-before规则解析
下一篇: Python列表
Abyjun
博客等级 码龄15年 10粉丝 18原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值