22.Volatile原理

news2024/10/6 8:24:41

文章目录

  • 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++;
                }
                // 每次执行完毕 -1
                latch.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解锁释放锁,作用于主内存的变量,允许其他线程获取该变量的锁并进行操作

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1711230.html

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

相关文章

定位器与PWM的LED控制

文章目录 一、STM32定时器二、脉宽调制pwm三、定时器控制led&#xff08;1&#xff09;实验内容&#xff08;2&#xff09;创建工程&#xff08;3&#xff09;Keli程序&#xff08;4&#xff09;观察波形图&#xff08;5&#xff09;实物连接图&#xff08;6&#xff09;实践效…

Python机器学习 Tensorflow + keras 实现CNN

一、实验目的 1. 了解SkLearn Tensorlow使用方法 2. 了解SkLearn keras使用方法 二、实验工具&#xff1a; 1. SkLearn 三、实验内容 &#xff08;贴上源码及结果&#xff09; 使用Tensorflow对半环形数据集分 #encoding:utf-8import numpy as npfrom sklearn.datasets i…

AI图书推荐:用ChatGPT和Python搭建AI应用来变现

《用ChatGPT和Python搭建AI应用来变现》&#xff08;Building AI Applications with ChatGPT API&#xff09;将ChatGPT API与Python结合使用&#xff0c;可以开启构建非凡AI应用的大门。通过利用这些API&#xff0c;你可以专注于应用逻辑和用户体验&#xff0c;而ChatGPT强大的…

clocking wizard IP核通过AXI4-Lite接口实现动态重新配置应用实例

在最近的FPGA应用中&#xff0c;应用到了基于Zynq 7000的Uart串口设计&#xff0c;为了让串口的时钟更精确&#xff0c;采用了外部时钟模式&#xff0c;如下图所示。外部时钟连接到了Clocking Wizard IP核的输出端。 在串口通信时&#xff0c;发现串口有错码出现。例如&#xf…

ADS基础教程16 - 存档和导入(workspace、cell、view)

设计加密保护IP 一、引言二、workspace归档二、Cell归档三、View归档四、导入归档文件 一、引言 介绍如何ADS中如何对workspace、cell和view进行存档&#xff0c;以及如何将存档文件导入到工程中。 二、workspace归档 (1)在菜单栏中&#xff0c;选择File–>Archive Works…

Follow Your Pose: Pose-Guided Text-to-Video Generation using Pose-Free Videos

清华深&港科&深先进&Tencent AAAI24https://github.com/mayuelala/FollowYourPose 问题引入 本文的任务是根据文本来生成高质量的角色视频&#xff0c;并且可以通过pose来控制任务的姿势&#xff1b;当前缺少video-pose caption数据集&#xff0c;所以提出一个两…

Chisel入门——在windows下vscode搭建|部署Scala2.13.3开发环境|用Chisel点亮FPGA小灯

文章目录 前言一、vscode搭建scala开发环境1.1 安装Scala官方插件Scala Syntax1.2 创建hello_world.scala文件1.3 确认java的版本(博主使用的是1.8)1.4 下载Scala Windows版本的二进制文件1.5 配置环境变量1.6 交互模式测试一下1.7 vscode运行scala 二、windows安装sbt2.1 下载…

Matlab|主动配电网故障恢复与孤岛划分模型【多时段】

目录 1 主要内容 1.1 模型目标 1.2 约束条件 2 部分代码 3 程序结果 4 下载链接 1 主要内容 程序主要方法复现《主动配电网故障恢复的重构与孤岛划分统一模型》&#xff0c;完全复现检修策略约束和潮流约束&#xff0c;辐射状与连通性约束考虑孤岛划分情形&#xff0c;采…

翻译《Use FILE_SHARE_DELETE in your shell extension》

在写 《翻译《The Old New Thing》- What did MakeProcInstance do?》 文章时&#xff0c;了解到了 Michael Geary &#xff0c;他也有不少优秀的技术文章&#xff0c;现翻译一篇关于文件操作的细节的文章 原文 Use FILE_SHARE_DELETE in your shell extension | mg.tohttps:…

【Unity之FGUI】白仙章Fairy GUI控件详解二

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;元宇宙-秩沅 &#x1f468;‍&#x1f4bb; hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍&#x1f4bb; 本文由 秩沅 原创 &#x1f468;‍&#x1f4bb; 收录于专栏&#xff1a;就业…

硬盘监控,保障硬盘性能

硬盘驱动器是个人计算机和服务器中用于存储数字数据的硬件部件&#xff0c;硬盘突然故障可能导致永久数据丢失&#xff0c;大多数硬盘驱动器使用自我监控、分析和报告技术&#xff08;SMART&#xff09;来跟踪各种性能指标并分析其自身的运行状况。然而&#xff0c;并不是所有的…

HackTheBox-Machines--Popcorn

文章目录 0x01 端口扫描0x02 测试思路2.1 80端口测试 0x03 /torrent 目录文件上传测试0x04 权限提升 Popcorn 测试过程 0x01 端口扫描 (base) gryphonwsdl ~ %nmap -sC -sV 10.129.138.22 Starting Nmap 7.94 ( https://nmap.org ) at 2024-05-28 14:22 CST Nmap scan report …

Linux shell编程学习笔记51: cat /proc/cpuinfo:查看CPU详细信息

0 前言 2024年的网络安全检查又开始了&#xff0c;对于使用基于Linux的国产电脑&#xff0c;我们可以编写一个脚本来收集系统的有关信息。对于中央处理器CPU比如&#xff0c;我们可以使用cat /proc/cpuinfo命令来收集中央处理器CPU的信息。 1. /proc/cpuinfo 保存了系统的cpu…

贵州大学24计算机考研数据速览,国家重点实验室22408复试线285分!贵州大学计算机考研考情分析!

贵州大学计算机科学与技术学院坐落在贵州大学北校区&#xff08;贵阳花溪&#xff09;。 学院现有教职工139人&#xff0c;其中专职教师126人&#xff0c;教授17人&#xff0c;副教授37人&#xff0c;讲师46人&#xff0c;高级实验师4人&#xff0c;实验师17人。具有博士学位的…

【易错题】数据可视化基础练习题(30道选择题)#CDA Level 1

本文整理了数据可视化基础知识相关的练习题&#xff0c;共30道&#xff0c;适用于想巩固数据可视化知识的同学&#xff0c;也可作为备考CDA一级的补充习题。来源&#xff1a;如荷学数据科学题库&#xff08;技术专项-可视化&#xff09;。 1&#xff09; 2&#xff09; 3&…

通义千问图像识别功能的23个实用案例

●给出穿搭建议 这位女士佩戴的是一款精致的长款耳坠&#xff0c;设计上融合了复古和现代元素。为了更好地搭配这款耳环&#xff0c;以下是一些建议&#xff1a; 服装风格&#xff1a;由于耳环本身具有一定的华丽感&#xff0c;建议选择简约而优雅的服装来平衡整体造型。可以选…

栈和队列专题(LeetCode)

目录 有效的括号题解代码加解释 用队列实现栈题解代码加解释 设计循环队列题解代码加解释 用栈实现队列题解代码加解释 有效的括号 题解 左括号从s字符串中取出来放入栈中 s中就只有右括号了 那么栈顶的左括号和s的右括号匹配即可 代码中也详细解释了左括号和右括号多少的问题…

如何成为快手外卖代理?本地生活服务平台加盟条件解析

近年来&#xff0c;以抖音、快手和小红书等为代表的互联网大厂纷纷进军本地生活领域&#xff0c;改变美团和饿了么二分天下的这一局面的同时&#xff0c;也让本地生活成为了众多创业者眼中的“香饽饽”。其中&#xff0c;快手凭借着其庞大的用户群体&#xff0c;让快手团购外卖…

关于本人VIP付费文章说明

郑重声明&#xff1a;我写博客只是为了记录分享经验 自从上次写完数据结构系列后我就一直没有登陆&#xff0c;目前也没打算继续开新内容。今天偶然发现我之前写的文章被设为vip文章&#xff0c;要vip解锁才能看&#xff0c;我很确定当初我发布的时候选择的是公开&#xff0c;…

LeetCode 474.一和零

没做出来&#xff0c;最后看了解析&#xff0c;看了半天才懂。 我一开始把这个题当成多重背包来做了&#xff0c;因为有0和1两个参数需要考虑&#xff0c;但是中间很多情况不知道怎么处理。后面看了解析才知道这是个01背包问题&#xff0c;0和1都是一个物品上的属性&#xff0c…