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

22.Volatile原理

文章目录

  • Volatile原理
    • 1.Volatile语义中的内存屏障
      • 1.1.volatile写操作的内存屏障
        • 1.1.1.StoreStore 屏障
        • 1.1.2.StoreLoad 屏障
      • 1.2.volatile读操作的内存屏障
        • 1.2.1.LoadStore屏障
        • 1.2.2.LoadLoad屏障
    • 2.volatile不具备原子性
        • 2.1.原理

Volatile原理

1.Volatile语义中的内存屏障

在Java代码中,volatile关键字主要又两层语义

  1. 不同线程对volatile变量的值具有内存可见性,就是一个线程修改了某个volatile变量的值,该值对其他线程立即可见。
  2. 禁止指令重排序

同时volatile关键字不仅能保证可见性,还能保证有序性,保证有序性是通过内存屏障指令来确保的。

JVM编译器会在生成字节码文件时,会在指令序列中插入内存屏障来禁止特定类型的CPU重排序。

JVM在处理volatile关键字修饰的变量时,会采取保守策略来确保内存可见性和有序性,这涉及到内存屏障(Memory Barrier)的使用。内存屏障是一种硬件层面的指令,用于确保某些内存访问操作的执行顺序,防止CPU的乱序执行对并发程序的正确性产生影响。对于volatile变量的读写,JVM会分别在读和写操作前后插入适当类型的内存屏障,确保以下几点:

  1. 全局可见性: 保证对volatile变量的写操作能立即对其他线程可见,即使在不同的CPU缓存中也是如此。这意味着写操作之后,修改的值会立刻刷出到主内存中。
  2. 禁止重排序: 防止编译器和CPU对涉及volatile变量的代码进行不必要的重排序,确保它们按照程序员指定的顺序执行。这对于依赖于特定顺序的并发控制逻辑至关重要。

基于保守策略的volatile操作内存屏障插入策略主要包括以下方面:

  • 写屏障(Store Memory Barrier): 在写入volatile变量之后插入。它的作用是确保在该屏障之前的所有普通写操作(非volatile)都已完成,并且将当前线程的工作内存中的volatile变量值刷新到主内存中。这样,任何读取该volatile变量的线程都能看到最新值。
    • 在每个volatile操作面插入一个StoreStore屏障
    • 在每个volatile操作面插入一个 StoreLoad屏障
  • 读屏障(Load Memory Barrier): 在读取volatile变量之前插入。它的作用是确保读取操作之后的加载不会被重排序到该屏障之前,并且使CPU读取主内存中的最新值,而不是使用缓存中的旧值,从而确保读取到的是最近一次写入的值,无论这个写入操作发生在哪个线程中。
    • 在每个volatile操作面插入一个LoadLoad屏障
    • 在每个volatile操作面插入一个 LoadStore屏障

这些屏障的联合使用确保了对volatile变量的读写操作具有原子性和全局有序性,尽管它们不保证复合操作(如count++)的原子性。这就是为什么即使在没有锁的情况下,volatile也能作为轻量级的同步机制,用于状态标记、双重检查锁定模式等场景。

1.1.volatile写操作的内存屏障

volatile写操作的内存屏障插入策略为:在每个volatile写操作前插入SotreStore(SS)屏障,在写操作之后加上StoreLoad屏障

1.1.1.StoreStore 屏障

定义与作用StoreStore屏障主要用于确保一个存储(写)操作在另一个存储操作之前完成。换句话说,它强制所有在该屏障之前的存储操作在该屏障之后的存储操作之前完成。这种屏障通常用于避免写-写冲突导致的数据不一致问题,尤其是在处理器有乱序执行能力的体系结构中。

  • 前面的写入不会重排序到后面
  • 前面的写指令完成后,高速缓存数据刷入主存
  • 后面的写操作不会排序到前面

应用场景: 例如,在实现某些类型的锁释放操作时,可能需要确保解锁操作前的所有写操作已经完成,以免新获得锁的线程看到不一致的状态。

1.1.2.StoreLoad 屏障

定义与作用StoreLoad屏障是Java内存模型中最强大的一种屏障,它确保在屏障之前的所有写操作(存储操作)在屏障之后的任何读操作(加载操作)之前完成。这意味着不仅要求写操作完成,而且要确保这些写操作对所有线程可见。因此,StoreLoad屏障通常用于实现volatile变量的写操作后,以确保写入的值对其他线程立即可见。

  • 前面的写不会重排序到后面
  • 前面的写指令操作完成后,高速缓存数据立即刷入主存
  • 让高速缓存的数据失效,重新从主存中加载数据,保证内核的高速缓存数据一致
  • 后面的读操作不会重排序到前面

应用场景StoreLoad屏障直接关联于Java中volatile字段的写操作实现。当一个线程修改了一个volatile变量的值,JVM会在写操作之后插入一个StoreLoad屏障,以确保该写入的值能够立即对其他线程可见,同时刷新处理器的缓存,避免数据的脏读。此外,它也常用于锁释放操作后的内存可见性保障,确保解锁前的内存更改对后续可能获得锁的线程是可见的。

总结来说,StoreStore屏障关注于维持存储操作之间的顺序,而StoreLoad屏障则进一步确保了写操作的全局可见性,并在写-读操作间建立了一个顺序关系,这对于维护多线程程序的一致性和正确性至关重要。

在这里插入图片描述

1.2.volatile读操作的内存屏障

volatile读操作的内存屏障插入策略为:在每个volatile读操作后面插入LoadLoad(LL)屏障和LoadStore屏障,禁止后面的普通读、普通写、和前面的volatile读操作发生重排序

1.2.1.LoadStore屏障

定义与功能LoadStore屏障,也称为读写屏障,其主要作用是确保屏障之后的读操作不会被重排序到屏障之前,且屏障之后的写操作不会被重排序到屏障之前的读操作之前。这意味着它不仅确保了读操作不会提前,还阻止了读之后的写操作与读操作之前的任何写操作发生乱序。这在volatile读操作的上下文中,意味着确保了读取到的volatile变量的值不会被之后的写操作所覆盖或影响,保持了读取操作的确定性。

  • 前面的读操作不会排到后面
  • 让高速缓存中的数据失效,重新从主存中加载数据
  • 后面的写操作不会排列到前面

在volatile读操作中的应用: 当执行volatile读操作时,Java虚拟机(JVM)会在读取操作之后插入一个LoadStore屏障。这个屏障的目的是确保当前线程的任何后续写操作不会与刚完成的volatile读操作交错,保证了volatile读的值不会因为之后的写而变得无效或不一致。同时,这也间接帮助确保了volatile读取操作后的写操作不会与之前的volatile读操作或普通读操作发生冲突,维护了操作的顺序性。

1.2.2.LoadLoad屏障

定义与功能LoadLoad屏障,或称为读读屏障,它的主要职责是防止屏障之后的读操作被重排序到屏障之前的任何读操作之前。这意味着它确保了在屏障之后执行的任何读操作不会因为CPU的乱序执行优化而提前到屏障之前执行。尽管LoadLoad屏障本身在某些JMM的描述中不常直接提及,但讨论内存屏障时,其概念往往隐含在维护读操作顺序性的讨论中。

  • 前面的读操作不会被排到后面
  • 让高速缓存中的数据失效,重新从主存中加载数据
  • 后面读操作不会排列到前面

在volatile读操作中的应用: 虽然直接提及LoadLoad屏障在volatile读操作后插入的情况较少见,通常强调的是LoadStoreStoreLoad屏障的作用,但理解其概念对于全面把握内存屏障如何维护顺序性是有帮助的。在volatile读操作的上下文中,可以抽象理解为,屏障的逻辑效果确保了读取volatile变量的值不会被之后的其他读取操作提前,保证了读取volatile变量的顺序性。不过,实际中,volatile读操作的关键在于通过StoreLoad屏障确保了对其他线程写入volatile变量的值立即可见,同时防止了volatile读与普通读写操作的不恰当重排序。

在这里插入图片描述

2.volatile不具备原子性

volatile能保证数据的可见性,但是volatile并不能完全保证数据的原子性。对于volatile类型的变量进行符合操作例如(i++),仍然会存在线程不安全的问题

/*** 使用 10个线程,每个线程进行1000次 ++操作,来观察成员变量的结果是否符合我们的预期*/
public class VolatileAddDemo {private volatile int num = 0;@Test@DisplayName("测试并发情况下 volatile原子性")public void testVolatileAdd() {CountDownLatch latch = new CountDownLatch(10);ExecutorService executorService = Executors.newFixedThreadPool(10);for (int i = 0; i < 10; i++) {executorService.submit(() -> {for (int j = 0; j < 1000; j++) {num++;}// 每次执行完毕 -1latch.countDown();});}// 等待全部线程执行完毕try {latch.await();} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println("最终的结果:" + num);System.out.println("预期的结果:10000");System.out.println("相差:" + (10000 - num));}}

在这里插入图片描述

2.1.原理

首先来看一下JMM对变量进行读取和写入的操作流程

在这里插入图片描述

对于非volatile修饰的普通变量来说,在读取变量的时候,JMM要求需要 报纸readload的顺序即可

但是,从主存中读取 x 、y 两个变量的值,可能的操作是 read x -> read y -> load y -> load x,它并不要求操作是连续的

对于关键字 volatile修饰的内存可见变量而言,具有两个重要的语义

  1. 使用volatile修饰的变量在变量值发生改变时,会立即同步到主存,并且让其他线程的变量副本失效
  2. 禁止指令重排序:用volatile修饰的变量在硬件层面上会通过在指令前后加入内存屏障来实现,编译器通过以下规则来进行实现的。
    • 使用volatile修饰的变量 **read(读取),load(加载),use(使用)**都是连续出现的,所以每次使用变量时都要从主存读取最新的变量值,替换私有内存的变量副本值
    • 对于同一个变量的**assign(赋值),store(存储),write(主存)**操作都是连续出现,所以每次对变量的修改都会立即同步到主存中

?但是思考一下,单线程下**( read,load,use)(assign,store,write)**同时出现没什么问题,但是在多线程并发执行的情况下,因为单个操作具备原子性,但是多个组合的话就不具备原子性了,还是有可能会出现脏数据。

下面通过图来了解一下 并发时可能发生产生脏数据的场景

在这里插入图片描述

对于复合操作,volatile变量是无法保证其原子性的,如果想要保证复合操作的原子性,那么就需要使用锁,并在在高并发场景下,volatile变量一定要和Java显示锁结合使用

这里补充介绍一下 JMM内存模型的 8个 操作

操作描述作用的对象
read读取把一个变量的值从主内存或高速缓存读到线程的工作内存中,准备下一步的load操作
load加载入把read操作从主内存读取的变量值放入线程的工作内存中的变量副本中,此时变量才对线程可见
use使用把工作内存中变量的值传递给执行引擎,作为运算的输入
assign赋值把执行引擎计算出的结果赋值给工作内存中的变量
store存储把工作内存中修改后的变量值写回到主内存中
write写出把store操作从工作内存中变量的值写入到主内存,使得其他线程可见
lock加锁作用于主内存的变量,标记变量为线程独占,确保同一时刻只有一个线程能执行lock和unlock之间的操作
unlock解锁释放锁,作用于主内存的变量,允许其他线程获取该变量的锁并进行操作

相关文章:

22.Volatile原理

文章目录 Volatile原理1.Volatile语义中的内存屏障1.1.volatile写操作的内存屏障1.1.1.StoreStore 屏障1.1.2.StoreLoad 屏障 1.2.volatile读操作的内存屏障1.2.1.LoadStore屏障1.2.2.LoadLoad屏障 2.volatile不具备原子性2.1.原理 Volatile原理 1.Volatile语义中的内存屏障 在…...

Vue 3中的v-for指令使用详解

Vue 3中的v-for指令使用详解 一、前言1. 基本语法2. 循环渲染对象3. 在组件中使用v-for4.普通案例5. 其他用法 二、结语 一、前言 在Vue 3中&#xff0c;v-for指令是一个非常强大且常用的指令&#xff0c;它用于在模板中循环渲染数组或对象的内容。本文将为您详细介绍Vue 3中v…...

GB-T 43694-2024 网络安全技术 证书应用综合服务接口规范

编写背景 随着网络技术的发展和信息化进程的加速&#xff0c;网络安全问题日益凸显。为了加强网络安全管理&#xff0c;提升网络服务的安全性和可靠性&#xff0c;GB-T 43694-2024《网络安全技术 证书应用综合服务接口规范》应运而生。这份文件是 网络安全领域的标准之一&…...

AI大模型:掌握未知,开启未来

AI大模型的工作原理 AI大模型是指通过大量数据和复杂算法训练出的能够理解和生成自然语言文本的人工智能模型。它们背后的核心技术主要包括深度学习、神经网络和自然语言处理。以下是详细的工作原理以及通俗易懂的类比&#xff1a; 1. 数据收集和预处理 AI大模型的训练首先需…...

【C语言习题】26.字符逆序

文章目录 1.描述2.解题思路3.具体代码 1.描述 输入描述: 将一个字符串str的内容颠倒过来&#xff0c;并输出。可以有空格 数据范围&#xff1a;1≤&#x1d459;&#x1d452;&#x1d45b;(&#x1d460;&#x1d461;&#x1d45f;)≤10000 1≤len(str)≤10000 输出描述&…...

windows和linux下的库文件比较

在Windows和Linux操作系统中&#xff0c;库文件&#xff08;lib、dll、.a、.so&#xff09;都扮演着重要的角色&#xff0c;但它们之间存在一些关键的区别。以下是这些库文件之间的主要差异&#xff1a; Windows lib 静态链接库&#xff08;Static Link Library&#xff09;…...

第七十九节 Java面向对象设计 - Java访问级别

Java面向对象设计 - Java访问级别 类简单名称是 class 关键字和 {)之间的名称。 当我们通过简单的名称引用一个类时&#xff0c;编译器在引用类所在的同一个包中查找该类声明。 我们可以使用全名来引用一个类如下。 com.w3cschool.Dog aDog;指定类的访问级别的一般语法是 &…...

Vue进阶之Vue项目实战(四)

Vue项目实战 出码功能知识介绍渲染器性能调优使用 vue devtools 进行分析使用“渲染”进行分析判断打包构建的产物是否符合预期安装插件使用位置使用过程使用lighthouse分析页面加载情况使用performance分析页面加载情况应用自动化部署与发布CI/CD常见的CI/CD服务出码功能 出码…...

fix leakage脚本

芯片的PPA追求是无止境的&#xff0c;因而在修时序的过程中我们需要对设计修复leakage&#xff0c;降低芯片的静态功耗。 以下分享一个典型的leakage脚本 set design 1 set version "V1" set date [exec date %m%d%H%M] set working_directory ${design}_${version}…...

MySQL中视图是什么,有什么作用

目录 一、视图的简介 1.1 什么是视图&#xff1f; 1.2 为什么使用视图&#xff1f; 1.3 视图有哪些规则与限制&#xff1f; 1.4 视图能否更新&#xff1f; 二、视图的创建 三、视图的作用 3.1 用视图简化复杂的联结 3.2 用视图格式化检索出的数据 3.3 用视图过滤数据…...

【面试题】JavaScript基础高频面试(下)

10、Javascript 闭包是什么,闭包形成的原因和闭包的用途 &#xff1f; 闭包&#xff08;Closure&#xff09;是 JavaScript 中的一个非常重要的概念。简单地说&#xff0c;闭包就是一个函数能够访问另一个函数的作用域。这是因为在 JavaScript 中&#xff0c;函数是一等公民&a…...

对于个人而言,大数据时代如何更好地管理自己的信息?

在大数据时代&#xff0c;管理个人信息变得尤为重要。以下是几个建议来更好地管理个人信息&#xff1a; 认识和了解自己的数字足迹&#xff1a;了解自己在互联网上的活动&#xff0c;包括浏览历史、社交媒体和在线购物数据等。通过查阅自己的帐户设置和隐私选项&#xff0c;可以…...

oj项目后端分析

1.菜单管理 我们菜单管理有菜单表(sys_menu)&#xff0c;还有用户角色表&#xff08;sys_role&#xff09;&#xff0c;菜单表是用于管理我们用户所拥有的权限&#xff0c;不同的用户所看到的页面是不一样的&#xff0c;由于一些用户他能够看到题库管理和考题管理&#xff0c;还…...

书籍学习|基于SprinBoot+vue的书籍学习平台(源码+数据库+文档)

书籍学习平台 目录 基于SprinBootvue的书籍学习平台 一、前言 二、系统设计 三、系统功能设计 1平台功能模块 2后台功能模块 5.2.1管理员功能模块 5.2.2用户功能模块 5.2.3作者功能模块 四、数据库设计 五、核心代码 六、论文参考 七、最新计算机毕设选题推荐 …...

AI学习指南数学工具篇-MATLAB中的凸优化工具

AI学习指南数学工具篇-MATLAB中的凸优化工具 在人工智能领域&#xff0c;凸优化是一个非常重要的数学工具&#xff0c;它在机器学习、深度学习、数据分析等领域都有着广泛的应用。而MATLAB作为一款强大的数学工具软件&#xff0c;提供了丰富的凸优化工具和函数&#xff0c;为用…...

散户如何参与期权交易?

期权就是股票&#xff0c;唯一区别标的物上证指数&#xff0c;会看大盘吧&#xff0c;期权交易两个方向认购做多&#xff0c;认沽做空&#xff0c;双向t0交易没了&#xff0c;期权交易跟期货一样&#xff0c;对的&#xff0c;玩的也是合约&#xff0c;唯一区别没有保证金不会爆…...

Unity Apple Vision Pro 开发(一):开发前期准备【软硬件要求 | 开发者模式 | 无线调试打包】

文章目录 &#x1f4d5;教程说明&#x1f4d5;硬件要求&#x1f4d5;软件要求⭐Xcode 15.2 及以上⭐visionOS 1.0 (21N301) SDK 或者更高版本⭐Unity 2022 LTS for Apple Silicon (2022.3.18f1及以上的版本)⭐Unity Pro/Unity Enterprise/Unity Industry的授权许可证 &#x1f…...

IGMP——组播成员端网络协议

目录 一.IGMP基本概念 &#xff08;1&#xff09;组播转发困境 &#xff08;2&#xff09;感知组播成员方式 &#xff08;3&#xff09;IGMP版本 二.IGMP各版本的区别与联系 &#xff08;1&#xff09;IGMPV1 1.普遍组查询报文 2.成员关系报告报文 3.IGMPV1报文格式 4…...

Java break细节(标签)

Java break细节(标签)continue也可以使用标签 break是用来跳出循环的。 当有多重循环时&#xff0c;可以配合标签来使用&#xff0c;决定跳出那一重循环。 尽量不要使用标签 1、不代标签时&#xff0c;默认跳出 break 所在的那重循环&#xff1a; 可见在 i 3 时&#xff0…...

远程户外监控组网方案,工业4G路由器ZR2000

户外监控无人值守4G工业路由器组网应用涉及工业自动化、数据传输和远程监控的重要领域。在户外没有光纤的情况下&#xff0c;想要让监控或传感器等设备联网&#xff0c;仅需一台4G工业路由器即可解决。以下是关于远程监控户外组网的详细分析与应用&#xff1a; 物联网应用场景 …...

MPNet:旋转机械轻量化故障诊断模型详解python代码复现

目录 一、问题背景与挑战 二、MPNet核心架构 2.1 多分支特征融合模块(MBFM) 2.2 残差注意力金字塔模块(RAPM) 2.2.1 空间金字塔注意力(SPA) 2.2.2 金字塔残差块(PRBlock) 2.3 分类器设计 三、关键技术突破 3.1 多尺度特征融合 3.2 轻量化设计策略 3.3 抗噪声…...

内存分配函数malloc kmalloc vmalloc

内存分配函数malloc kmalloc vmalloc malloc实现步骤: 1)请求大小调整:首先,malloc 需要调整用户请求的大小,以适应内部数据结构(例如,可能需要存储额外的元数据)。通常,这包括对齐调整,确保分配的内存地址满足特定硬件要求(如对齐到8字节或16字节边界)。 2)空闲…...

多场景 OkHttpClient 管理器 - Android 网络通信解决方案

下面是一个完整的 Android 实现&#xff0c;展示如何创建和管理多个 OkHttpClient 实例&#xff0c;分别用于长连接、普通 HTTP 请求和文件下载场景。 <?xml version"1.0" encoding"utf-8"?> <LinearLayout xmlns:android"http://schemas…...

java调用dll出现unsatisfiedLinkError以及JNA和JNI的区别

UnsatisfiedLinkError 在对接硬件设备中&#xff0c;我们会遇到使用 java 调用 dll文件 的情况&#xff0c;此时大概率出现UnsatisfiedLinkError链接错误&#xff0c;原因可能有如下几种 类名错误包名错误方法名参数错误使用 JNI 协议调用&#xff0c;结果 dll 未实现 JNI 协…...

《用户共鸣指数(E)驱动品牌大模型种草:如何抢占大模型搜索结果情感高地》

在注意力分散、内容高度同质化的时代&#xff0c;情感连接已成为品牌破圈的关键通道。我们在服务大量品牌客户的过程中发现&#xff0c;消费者对内容的“有感”程度&#xff0c;正日益成为影响品牌传播效率与转化率的核心变量。在生成式AI驱动的内容生成与推荐环境中&#xff0…...

江苏艾立泰跨国资源接力:废料变黄金的绿色供应链革命

在华东塑料包装行业面临限塑令深度调整的背景下&#xff0c;江苏艾立泰以一场跨国资源接力的创新实践&#xff0c;重新定义了绿色供应链的边界。 跨国回收网络&#xff1a;废料变黄金的全球棋局 艾立泰在欧洲、东南亚建立再生塑料回收点&#xff0c;将海外废弃包装箱通过标准…...

Java入门学习详细版(一)

大家好&#xff0c;Java 学习是一个系统学习的过程&#xff0c;核心原则就是“理论 实践 坚持”&#xff0c;并且需循序渐进&#xff0c;不可过于着急&#xff0c;本篇文章推出的这份详细入门学习资料将带大家从零基础开始&#xff0c;逐步掌握 Java 的核心概念和编程技能。 …...

GC1808高性能24位立体声音频ADC芯片解析

1. 芯片概述 GC1808是一款24位立体声音频模数转换器&#xff08;ADC&#xff09;&#xff0c;支持8kHz~96kHz采样率&#xff0c;集成Δ-Σ调制器、数字抗混叠滤波器和高通滤波器&#xff0c;适用于高保真音频采集场景。 2. 核心特性 高精度&#xff1a;24位分辨率&#xff0c…...

C++使用 new 来创建动态数组

问题&#xff1a; 不能使用变量定义数组大小 原因&#xff1a; 这是因为数组在内存中是连续存储的&#xff0c;编译器需要在编译阶段就确定数组的大小&#xff0c;以便正确地分配内存空间。如果允许使用变量来定义数组的大小&#xff0c;那么编译器就无法在编译时确定数组的大…...

C#中的CLR属性、依赖属性与附加属性

CLR属性的主要特征 封装性&#xff1a; 隐藏字段的实现细节 提供对字段的受控访问 访问控制&#xff1a; 可单独设置get/set访问器的可见性 可创建只读或只写属性 计算属性&#xff1a; 可以在getter中执行计算逻辑 不需要直接对应一个字段 验证逻辑&#xff1a; 可以…...