谈谈Volatile

谈谈对volatile的理解 1,volatile关键字,修饰变量,是java虚拟机提供的一个轻量级同步机制,主要有以下三个特性: 1.1可见性 1.2不保证原子性 1.3禁止指令重排 1.1可见性的理解 由于jvm运行程序的实体是线程,而每个线程创建时jvm都会为其搭建一个工作内存,工作内存是每个线程的私有化数据区域,而JMM(java内存模型)中规定所有的变量都存储在主内存中,主内存有共享内存区域,所有线程都可以访问,但线程对变量的操作(读取赋值等)必须在工作内存中进行,首先要将变量... 阅读详情

Volatile 作用

  1. 可见性
  2. 禁止指令重排序

1. 可见性

想要深入理解Volatile的作用,首先要理解计算机组成原理的一些知识:

多核CPU结构

存储器层级结构

L1: 寄存器
L2: 高速缓存
L3: 高速缓存
L4: 主存
L5: 磁盘
L6: 远程文件存储

CPU的多级缓存

计算机由于Cpu的运行速度远远大于数据IO的速度,因此产生了缓存的机制。每一个CPU独占L1和L2,共享L3。一个变量会被首先拷贝到各自的独立的告诉缓存中,再参与ALU的运算。
在这里插入图片描述
当一个变量同时参与到两个CPU进行计算时,由于L1和L2的副本只是对主存被数据的拷贝,因此不同CPU中同一个变量存在不一致的现象。CPU1和CPU2对自身内存中变量副本的更改,对方是不知道的。
Volatile的可见性的目的就是使得CPU知道当前变量在其他线程中已经被改变,本地副本失效,需要重新从主存中进行读取。

在这里引入一些其他知识, CPU在读取数据的过程中往往不是只读取一个字节的数据,而是以块的形式来读取的:
现在计算机中往往按照块的形式从内存中往高速缓存中拷贝数据, 对数据进行预热。这个数据块也被叫做缓存行,目前主流与的缓存行的大小为64字节(byte)。

依据的是程序的局部性原理

  1. 时间局部性是指如果程序中的某条指令一旦执行,则不久之后该指令可能再次被执行;如果某数据被访问,则不久之后该数据可能再次被访问。
  2. 空间局部性是指一旦程序访问了某个存储单元,则不久之后。其附近的存储单元也将被访问

缓存一致性协议

从上一节可以知道,在没有任何同步的情况下,多个CPU同时操作内存中的同一个变量会出现变量不一致的情况。此时人们就提出了缓存一致性协议,通过它使得多核CPU可以操作一个变量。缓存一致性协议中比较有名的是Inter的MESI协议。

缓存一致性协议有MSI,MESI,MOSI,Synapse,Firefly及DragonProtocol等等,MESI协议只是其中一种。

MESI

MESI是以缓存行(Cache Line)的几个状态来命名的(全名是Modified、Exclusive、 Share or Invalid)。该协议要求在每个缓存行上使用两个bit位来维持,使得每个数据单位可能处于M、E、S和I这四种状态之一。

状态含义监听
Modified该Cache line有效,数据被修改了,和内存中的数据不一致,数据只存在于本Cache中。缓存行必须时刻监听所有试图读该缓存行相对就主存的操作,这种操作必须在缓存将该缓存行写回主存并将状态变成S(共享)状态之前被延迟执行。
Exclusive该Cache line有效,数据和内存中的数据一致,数据只存在于本Cache中。缓存行也必须监听其它缓存读主存中该缓存行的操作,一旦有这种操作,该缓存行需要变成S(共享)状态。
Share该Cache line有效,数据和内存中的数据一致,数据存在于很多Cache中。缓存行也必须监听其它缓存使该缓存行无效或者独享该缓存行的请求,并将该缓存行变成无效(Invalid)。
Invalid该Cache line无效。

伪共享问题(False Shared):当两个变量处于同一缓存行时,由于CPU的行读取策略,即使两个线程没有同时操作同一个变量,也会触发缓存一致性协议,从而降低效率。
此时可以通过缓存行对齐来解决这个问题,在disruptor源码中,对RingBuffer的指针定义前后,分别插入了7个 long类型变量,以保证指针所在的缓存行只有这一个操作数。(long类型变量的长度为8个字节, 7*8 = 56字节,再加上本身的长度,共占64个字节)

至此,关于Volatile的可见性问题应该都能够理解了。

注意:缓存一致性协议实际与Volatile的关系并不大,无论变量是否声明volatile,缓存一致性协议都会执行,此处提及缓存一致性协议仅是为了能更好的理解Volatile。

2. 禁止指令重排序

首先要了解什么是指令重排序,一般来说Java 代码在运行过程中,为了优化运行效率,在不影响程序结果的情况下,会对指令进行重排序,重排序可能会发生在以下两个场景里:

  1. Java 代码编译时
  2. Cpu执行指令时

如何定义不影响程序执行结果:
JMM通过happened-before原则,即指导了程序员如何编写程序又没有限制JVM在对实现优化的具体方法上做出严格的限制。

接下来,我们通过代码来一步步探索Volatile是如何做到禁止指令重排序的

Java 源码

package com.MultiThread;

public class Testvolatile {

    public volatile int[] cnt = new int[2];

    public static void main(String[] args) {
        Testvolatile t = new Testvolatile();
        System.out.println(t.cnt);
    }
}

编写以上Java 类,通过javac将其编译成class文件,然后通过javap -verbose -p Testvolatile来查看生成的Class文件。

字节码

public class com.MultiThread.Testvolatile
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #7.#18         // java/lang/Object."<init>":()V
   #2 = Fieldref           #3.#19         // com/MultiThread/Testvolatile.cnt:[I
   #3 = Class              #20            // com/MultiThread/Testvolatile
   #4 = Methodref          #3.#18         // com/MultiThread/Testvolatile."<init>":()V
   #5 = Fieldref           #21.#22        // java/lang/System.out:Ljava/io/PrintStream;
   #6 = Methodref          #23.#24        // java/io/PrintStream.println:(Ljava/lang/Object;)V
   #7 = Class              #25            // java/lang/Object
   #8 = Utf8               cnt
   #9 = Utf8               [I
  #10 = Utf8               <init>
  #11 = Utf8               ()V
  #12 = Utf8               Code
  #13 = Utf8               LineNumberTable
  #14 = Utf8               main
  #15 = Utf8               ([Ljava/lang/String;)V
  #16 = Utf8               SourceFile
  #17 = Utf8               Testvolatile.java
  #18 = NameAndType        #10:#11        // "<init>":()V
  #19 = NameAndType        #8:#9          // cnt:[I
  #20 = Utf8               com/MultiThread/Testvolatile
  #21 = Class              #26            // java/lang/System
  #22 = NameAndType        #27:#28        // out:Ljava/io/PrintStream;
  #23 = Class              #29            // java/io/PrintStream
  #24 = NameAndType        #30:#31        // println:(Ljava/lang/Object;)V
  #25 = Utf8               java/lang/Object
  #26 = Utf8               java/lang/System
  #27 = Utf8               out
  #28 = Utf8               Ljava/io/PrintStream;
  #29 = Utf8               java/io/PrintStream
  #30 = Utf8               println
  #31 = Utf8               (Ljava/lang/Object;)V
{
  public volatile int[] cnt;
    descriptor: [I
    flags: ACC_PUBLIC, ACC_VOLATILE

  public com.MultiThread.Testvolatile();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: aload_0
         5: iconst_2
         6: newarray       int
         8: putfield      #2                  // Field cnt:[I
        11: return
      LineNumberTable:
        line 3: 0
        line 5: 4

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=2, args_size=1
         0: new           #3                  // class com/MultiThread/Testvolatile
         3: dup
         4: invokespecial #4                  // Method "<init>":()V
         7: astore_1
         8: getstatic     #5                  // Field java/lang/System.out:Ljava/io/PrintStream;
        11: aload_1
        12: getfield      #2                  // Field cnt:[I
        15: invokevirtual #6                  // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
        18: return
      LineNumberTable:
        line 8: 0
        line 9: 8
        line 10: 18
}

在字节码中可以看到

public volatile int[] cnt;
    descriptor: [I
    flags: ACC_PUBLIC, ACC_VOLATILE

volatile在编译过程中被翻译成了ACC_VOLATILE.
通过对指令集查表可知:
在这里插入图片描述

JVM 内存屏障

继续往下,在JVM中定义了以下4种内存屏障的来解释volatile:

  1. LoadLoad
  2. LoadStroe
  3. StroeStore
  4. StroeLoad

简单来讲LoadLoad的快速记忆方法就是上一条Load指令和下一条Load指令不能互换顺序,其他三条同理。

实际实现:
-----StoreStoreBarrier------
volatile 写
----- StoreLoadBarrier-------

-----LoadLoadBarrier------
volatile 读
----- LoadStoreBarrier-------

汇编

以HotSpot为例,内存屏障最终会被转译成以下的汇编指令:
lock addl $0×0,(%rsp)
lock : LOCK指令会使紧跟在其后面的指令变成 原子操作(atomic instruction)。暂时的锁一下总线,指令执行完了,总线就解锁了,同时lock还有一个语义:就是释放锁之后CPU必需同步一次内存

其意义就是通过对当前的操作数做了一次+0运算,将其刷新回主存。
即volatile的最底层实现实际是通过总线锁的形式实现的,但是为什么不适用CPU级别的内存屏障呢,可能的原因是lock的普适性更好,目前的CPU都支持总线锁吧。

CPU级内存屏障
以Inter为例存在3条CPU内存屏障指令:
①sfence:也就是save fence,写屏障指令。在sfence指令前的写操作必须在sfence指令后的写操作前完成。
②lfence:也就是load fence,读屏障指令。在lfence指令前的读操作必须在lfence指令后的读操作前完成。
③mfence:在mfence指令前得读写操作必须在mfence指令后的读写操作前完成。

参考资料:

https://b23.tv/bpNY6m
https://blog.youkuaiyun.com/u013928208/article/details/109166440
https://www.cnblogs.com/minikobe/p/12162415.html
https://blog.youkuaiyun.com/a772304419/article/details/103940426

四阶龙格库塔法求解一次常微分方程组(python实现) 之前在博客发布了关于使用四阶龙格库塔方法求解一次常微分方程组的文章,由于代码缺少具体的验证,部分朋友可能存在疑问,因此这里打算再重新写一篇博客来验证一下程序的正确性,另外,这里是使用python语言来实现的。 阅读详情

相关推荐

深入解析LVDS电平标准在LCD屏接口设计中的关键作用

本文深入解析了LVDS电平标准在LCD屏接口设计中的核心作用。文章不仅阐释了LVDS低电压差分信号的工作原理、关键电气参数及抗噪优势,还详细对比了VESA与JEIDA两种主流数据格式,并提供了从PCB布局布线到信号调试的实战避坑指南,是确保高速、稳定显示的关键技术参考。

weixin_28419039的博客 335

volatile面试题总结

volatile作用 可见性:当一个线程修改了声明为volatile变量的值,新值对于要读该变量的线程来说是立即可见的 有序性:代码的执行禁止指令重排序 可见性和原子性的区别 原子性:一个或多个操作在CPU执行过程中不中断称为原子性。对于volatile变量的读/写操作具有原子性,但是复合操作没有,比如i++。所以volatile并不保证原子性。 volatile在JMM中的可见性原理 在Java内存模型(JMM,Java Memory Model)中,所有的变量都存储在祝内存,每条线程都有.

西溪少女的梦 365

LangChain实战:用FAISS搭建本地文档问答系统(含Word文件处理避坑指南)

本文详细介绍了如何利用LangChain框架和FAISS向量数据库构建本地化智能文档问答系统,特别针对Word文档处理中的技术难点提供实用解决方案。通过模块化设计、本地化支持和多格式兼容,实现高效文档处理与精准问答,满足企业对数据隐私和安全的需求。

o4p5q6r7s的博客 410

2、volatile关键字(并发编程)

###CPU术语 1、内存屏障:是一组处理器指令。在程序中添加volatille关键字,JVM解释成汇编指令的时候,会在该变量前加上lock指令。lock指令会在该段内存中,加上内存屏障,防止CPU的重排序。 2、缓冲行:CPU执行非常快,操作主内存的时候,主内存根本扛不住。所以会在CPU和主内存之间加了一个缓冲区。CPU将数据写入缓冲行L1,L2,L3,但不是马上写入主内存中。异步操作。 3、原...

u011603018的博客 207

基于STM32设计的太阳能智慧路灯_444

本文设计并实现了一种基于 STM32 的太阳能智慧路灯系统。系统以 STM32F103C8T6 单片机为核心,集成温湿度检测、空气质量检测、环境光照检测、行人检测和路灯故障检测等功能,实现对路灯运行环境和工作状态的实时感知。

464

【Flink】FlinkSQL如何触发执行计算

Table (通过 StatementSet.addInsert() 输出给某个 Sink)和 INSERT 语句 (通过调用 StatementSet.addInsertSql())会先被缓存到 StatementSet 中,StatementSet.execute() 方法被调用时,所有的 sink 会被优化成一张有向无环图。该方法是用来将一个表的内容插入到目标表中,一旦该方法被调用, TABLE API 程序立即被翻译。该方法是用来执行一个 SQL 语句,一旦该方法被调用, SQL 语句立即被翻译。

一杯咖啡半杯糖的博客 1007

浅谈volatile关键字

Java的volatile关键字在JDK源码中经常出现,但是对它的认识只是停留在共享变量上,今天来谈谈volatile关键字。volatile,从字面上说是易变的、不稳定的,事实上,也确实如此,这个关键字的作用就是告诉编译器,只要是被此关键字修饰的变量都是易变的、不稳定的。

x 1万+

基于YOLO26深度学习框架的人脸识别门禁系统的算法研究实现方法

本文研究了基于YOLO26深度学习框架的人脸识别门禁系统实现方法。系统采用YOLO26目标检测算法,通过CSPDarknet53主干网络、特征金字塔结构和注意力机制实现高效人脸检测。研究内容包括数据预处理、模型训练优化和身份验证等关键技术,使用迁移学习策略和CIoU损失函数提升检测精度。该系统可应用于智能安防场景,具有实时性强、识别准确的特点,为门禁管理提供了智能化解决方案。

一键难忘的博客 766

C语言中volatile关键字的作用

一.前言 1.编译器优化介绍: 由于内存访问速度远不及CPU处理速度,为提高机器整体性能,在硬件上引入硬件高速缓存Cache,加速对内存的访问。另外在现代CPU中指令的执行并不一定严格按照顺序执行,没有相关性的指令可以乱序执行,以充分利用CPU的指令流水线,提高执行速度。以上是硬件级别的优化。再看软件一级的优化:一种是在编写代码时由程序员优化,另一种是由编译器进行优化。编译器优化常用的方法有:将内...

小白编程 8181

深度强化学习训练结果曲线绘制教程

使用python语言,只调用matplotlib库,绘制训练过程中保存的强化学习训练曲线。模仿经典论文中的训练曲线,多次训练取平均,并绘制出阴影区域。

加斯顿工程师的博客 911

【推荐100个unity插件】动画重定向,快速在不同角色间共享动画,共用非人形Humanoid(泛型generic)动画——Retarget Pro

Retarget Pro是一款高效的Unity动画重定向工具,可快速在不同角色间共享动画。主要特点包括:1分钟快速设置,支持编辑器和运行时重定向;适用于人形、动物、武器等各类角色;提供基础重定向、IK重定向和FPS专用重定向三种功能模块;通过Rig组件和骨架资产实现骨骼链匹配,支持批量处理。插件包含示例项目和预设资源,操作简便且效果优于Unity原生Humanoid系统,特别适合处理手指等精细动画。

向宇的博客,专注php/web全栈 unity游戏开发,欢迎大家评论纠错 548

C语言中volatile关键字

volatile有什么含义?有什么用法? 官方定义是: 一个变量也许会被后台程序改变。 关键字volatile与const绝对独立。它指示一个变量也许会被某种方式修改,这种方式按照正常程序流程分析是无法预知的(例如,一个变量也许会被一个中断服务程序所修改)。这个关键字使用以下语法定义: volatile data-defiinition; 注:变量如果加了voletile修饰,则会从内存中重新装...

joy2017的博客 3万+

efm32 入门.pdf

本PDF共计22页,介绍的是操作驱动API,下载与否自己斟酌。

谈谈对Volatile的理解

volatile是Java虚拟机提供的一个轻量级的同步机制; 它有三大特性: 1).保证可见性 2.不保证原子性 3.禁止指令重排序 1.先要提及关于JMM(Java Memary model)模型的理解。也就是两个线程之间如何通信的问题。所有的数据都会经过主内存进行调用,主内存对应的硬件及是我们的内存条。当多个线程进入时,他们复制主内存的变量,到本地进行修改,然后通知给主内存,由有主内存通知其他...

qq_35529931的博客 1117

IEM模型,地表粗糙度模拟

利用微波反演土壤水分时候,利用IEM模型可以实现对随机地表的模拟

OV9734 DATASHEET,完整规格书

OV9734完整规格书,包括电气参数,尺寸规格,寄存器配置表等

谈谈你对volatile的理解

一、volatile特性: volatile是Java虚拟机提供的轻量级的同步机制。主要有三大特性: 保证可见性 不保证原子性 禁止指令重排序 1、保证可见性 1)代码演示 AAA线程修改变量number的值为60,main线程获取到的number值是0,就一直循环等待。 原因:int number = 0;number变量之前没有添加volatile关键字,没有可见性。添加volatile关键字...

MrShanQIu的博客 506

复分析 阿尔福斯(Ahlfors) 第三版答案 英文

答案分为十章,有部分习题没有答案,全英文复分析阿尔福斯(Ahlfors) 第三版答案

谈谈volatile

内存屏障(Memory Barrier 或 Memory Fence)是一种硬件级别的同步操作,它强制处理器按照特定顺序执行内存访问操作,确保内存操作的顺序性,阻止编译器和 CPU 对内存操作进行不必要的重排序。内存屏障可以确保跨越屏障的读写操作不会交叉进行,以此维持程序的内存一致性模型。在 Java 内存模型(JMM)中,volatile 关键字用于修饰变量时,能够保证该变量的可见性和有序性。

zainful的博客 1054

Java 谈谈什么是 volatile

随着互联的飞速发展,互联网公司也越来越高,并发多线程,内存管理,JVM调优等成为面试必问题。 1. 什么是 volatile? volatile 是 Java 虚拟机提供的轻量级的同步机制。 他的三大特性: 保证可见性 不保证原子性 禁止指令重排 1.1 可见性 在解释可见性之前需要先看一下什么是 JMM? JMM(Java 内存模型 Java Memory Model,简称 ...

cong____cong的博客 1724

DeepSeek大模型演进:从MoE架构到FP8训练优化

混合专家系统(MoE)作为大语言模型的核心架构,通过动态路由机制实现计算资源的智能分配。其技术原理在于将传统密集模型分解为多个专家模块,结合门控网络实现条件计算,在保持激活参数量可控的前提下显著扩展模型容量。这种架构特别适合数学推理、代码生成等需要复杂逻辑处理的任务场景,DeepSeek V3通过分层路由策略和负载均衡优化,将专家利用率标准差降低至0.17。配合FP8低精度训练体系,模型在保持87.1% MMLU准确率的同时,训练效率达到205 TFlops,展现了MoE架构在大规模语言模型领域的工程实践价

weixin_33850015的博客 398

Java当中谈谈你对volatile的理解

什么是内存屏障?如何保证可见性与有序性

syh_IT 304

谈谈 volatile

什么是 volatile volatile (易变型变量)是一个类型修饰符 volatile 的变量是说这变量可能会被意想不到地改变,这样,编译器就不会去假设这个变量的值 volatile 的作用 volatile 的作用是作为指令关键字,确保本条指令不会因编译器的优化而省略,且要求每次直接读值,简单地说就是防止编译器对代码进行优化 用它声明的类型变量表示变量本身可以被某些编译器未知的因素更改,比...

qq_44759710的博客 370

请谈谈你对volatile的理解

Java面试必问

qq_43765199的博客 307
上一篇: 线程池能够持有最大线程数量真的是Integer.MAX_VALUE吗?
下一篇: 浅谈Synchronized
Fly_TheWind
博客等级 码龄11年 53粉丝 110原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值