掌握Java中的volatile关键字

news2024/11/18 17:37:19

高速缓存

什么是高速缓存

高速缓存(Cache)是一种用于存储计算机数据的临时存储设备,用于加速数据访问速度,减少对主存储器(RAM)或磁盘的频繁访问。高速缓存通过将最常用的数据存储在更接近CPU的位置,从而提供更快的数据检索速度,从而提高系统性能。

引入高速缓存的目的

计算机在运行程序时首先将程序从磁盘读取到主存,然后CPU按规则从主存中取出指令、数据并执行指令,但是随着时间的推移,CPU 和内存的访问性能相差越来越大 ,直接从主存(一般用DRAM制成)中读写是很慢的,所以我们引入了cache。
在执行程序前,首先会试图把要用到的指令、数据从主存移到cache中,然后在执行程序时直接访问cache。

高速缓存的分类

在这里插入图片描述
L1 Cache(一级缓存)
位于处理器核心内部。
通常分为数据缓存(D缓存)和指令缓存(I缓存)。
用于存储处理器核心频繁使用的数据和指令。
具有最快的访问速度,但容量较小。

L2 Cache(二级缓存)
通常位于处理器核心之间,多个核心共享。
更大容量的缓存,可存储更多数据和指令。
访问速度比L1 Cache慢一些,但比主存储器快。

L3 Cache(三级缓存)
通常位于多个CPU核心之间,多个处理器共享。
容量更大,可用于共享数据和指令。
访问速度通常比L2 Cache慢,但仍比主存储器快。

主存(Main Memory)
也被称为RAM(随机访问存储器)。
比L3 Cache慢,但仍提供了较高的带宽和存储容量。
通常用于存储程序和数据,是CPU主要的数据来源之一。

引入高速缓存导致了什么问题

引入高速缓存可以显著提高计算机系统的性能和响应速度但同时也可能引发以下一些问题:
一致性问题: 高速缓存引入了一致性问题,即在多个缓存副本之间保持数据的一致性。当一个核心修改了缓存中的数据时,其他核心的缓存需要知道这个更改,以避免数据不一致的情况。一致性协议(如MESI协议)用于解决这个问题,但会引入一定的性能开销。

缓存污染: 当高速缓存大小有限时,可能会出现缓存污染问题。某些数据块可能被频繁地加载到缓存中,而其他重要数据却无法进入缓存,从而导致性能下降。

缓存争用: 多个核心同时尝试访问共享缓存时,可能会导致缓存争用问题。这可能会导致性能下降,因为核心必须等待缓存访问权限。

一致性开销: 维护缓存一致性需要额外的开销,包括缓存一致性协议的开销以及在核心之间传输一致性信息的开销。

缓存失效: 缓存中的数据可能会由于缓存不命中而失效,导致需要从主存储器中重新加载数据。这会引入访问延迟。

复杂性: 高速缓存管理和一致性是复杂的问题,需要复杂的硬件和软件支持。这增加了系统的复杂性和维护难度。

缓存一致性问题

什么是缓存一致性问题

CPU缓存一致性问题是指在多处理器或多核系统中,每个处理器核心具有自己的高速缓存(Cache),而共享相同主内存(Main Memory)的数据可能存在多个缓存中的不一致性。这可能导致在并发多线程或多进程环境中,数据的不一致性和错误结果。

Processor02修改缓存x=20的值改为40 同步到本地缓存行 但是没同步到主内存中 所以Processor0读到的还是X=20是之前的值所以造成了缓存的不一致性
在这里插入图片描述

如何解决缓存一致性问题

总线锁

什么是总线

cpu与内存 输出/输入设备传递信息的公共通道 当cpu访问内存进行数据交互时 必须经过总线来传输

什么是总线锁

总线锁是通过系统总线(通常是内存总线)来实现的。当一个核心要写入某个内存地址时,它会向总线发送锁定请求。
总线锁会锁定整个总线,阻止其他核心访问内存,直到请求核心完成写操作。
这可以确保在总线上只有一个核心能够访问内存,从而避免了并发写入导致的数据不一致性。
但是总线锁锁定的是整个总线 期间会阻止其他核心访问内存所以他的消耗特别高

缓存锁

缓存锁是通过处理器核心的高速缓存来实现的。当一个核心要写入某个内存地址时,它会向其他核心发出缓存锁定请求。
如果其他核心已经缓存了相同的内存地址,它会释放该数据的缓存行,让请求核心获得独占访问。
这可以防止多个核心同时修改同一内存地址,从而确保数据一致性。
如果当前cpu访问的数据已经被缓存到其他cpu高速缓存中 那么 cpu不会在总线上声明Lock#信号 而是采用缓存一致性协议来保证多个cpu的缓存一致性

缓存一致性协议

什么是缓存一致性

M(Modify)
表示共享数据只缓存在当前CPU缓存中 并且是被修改状态 缓存的数据和主内存的数据不一致
E(Exclusive)
表示缓存的独占状态 数据只缓存在当前CPU缓存中 并且没有被修改
S(Shared)
表示数据可能被多个CPU缓存 并且各个缓存中的数据和主内存数据一致
I(Invalid)
表示缓存已经失效

缓存一致性遵守的规则

如果一个缓存行处于M状态 则必须监听所有试图获取该缓存行对应的主内存地址的操作 如果监听这类操作的发生 则必须在该操作 之前把缓存行中的数据写回主内存

如果缓存行处于S状态 那么则必须要监听使该缓存行状态设置为Invalid 或者对缓存行执行Exclusive操作的请求 如果存在则必须要把当前缓存行状态设置为Invalid

如果一个缓存行状态为E状态 那么它必须要监听其他试图读取该缓存行对应的主内地址的操作一旦有这种操作该缓存行需要设置成Shared

监听过程由总线嗅探协议完成

有了缓存一致性之后数据读取流程

1.Cpu0发出一条从内存中读取X变量的指令 主内存通过总线返回数据后缓存到CPU0的高速缓存中 将状态设置成E
2.如果此时CPU1发出对变量X的读取指令 那么当CPU0检测到缓存地址冲突就会针对该消息作出响应,将缓存在cpu0的x的值通过ReadResponse消息返回给CPU1 此时X分别存在CPU0和CPU1的高速缓存中所有X的状态为S
3.然后CPU0把X变量的值修改成x=30 把自己的缓存行状态设置成E 接着把修改后的数据写入内存中 此时X的缓存行是共享状态 同时需要发送一个Invalidate消息给其他缓存 Cpu1收到消息后把高速缓存的x置为Invalid状态

指令重排序

什么是指令重排序

指令重排序是一种优化编译器和处理器执行指令的技术,它可以改变指令的执行顺序,以提高程序的性能。然而,指令重排序可能导致多线程编程中出现意外的行为,因此需要在某些情况下加以控制。

指令重排序引发的问题

指令重排序问题的核心是:在多核处理器中,由于编译器和处理器会尽力提高指令的执行效率,导致在不改变程序语义的前提下,重新排列指令的执行顺序。这可能会引发以下问题:
可见性问题:当一个线程对某个共享变量进行写操作,其他线程可能无法立即看到这个写入的结果。这会导致线程间通信问题,例如一个线程修改了共享变量,但其他线程仍然读取了旧的值。

并发性问题:由于指令重排序,可能会导致多线程之间发生意外的交互,从而破坏了预期的执行顺序。

指令重排序阶段

编译器重排序:编译器在生成机器代码时可能会对源代码中的指令进行重新排序,以提高执行效率。这种重排序通常是基于编译器的优化,但需要确保不改变程序的语义。

处理器重排序:处理器在执行指令时,可能会对指令进行重新排序以充分利用处理器内部资源,例如乱序执行。这种重排序是在不改变程序语义的前提下,通过并行执行来提高性能。

内存重排序:处理器与内存之间的数据传输也可能导致重排序。处理器可以对内存访问指令进行重排序,从而影响多线程环境下的可见性。

as-if-serial语义

as-if-serial" 是 Java 内存模型(Java Memory Model,JMM)的一个重要概念。它表示在多线程环境下,程序执行的结果必须与某个顺序一致的串行执行结果一致。
具体来说,“as-if-serial” 原则保证了编译器、处理器和运行时环境不会改变代码中不存在数据竞争的操作的执行顺序。这意味着虽然编译器和处理器可以对指令进行重排序,但不能改变数据竞争操作的执行顺序。

“as-if-serial” 原则的主要目标是确保多线程程序的行为与单线程程序一致。在单线程程序中,所有操作都是串行执行的,因此多线程程序在各个线程中的操作也必须具有某种有序性。
举例来说,考虑以下代码:

int x = 1;
int y = 2;
int result = x + y;

在单线程环境中,这三个操作是按照顺序执行的。在多线程环境中,编译器和处理器可能会对这些操作进行重排序,但不能改变它们的执行顺序。这意味着不会出现如下情况:线程 A 的 x 操作在线程 B 的 y 操作之后执行,从而导致错误的结果。

总之,“as-if-serial” 原则是 Java 内存模型中的一个重要概念,它确保了多线程程序的行为与单线程程序一致。

重排序对多线程的影响

现在让我们来看看,重排序是否会改变多线程程序的执行结果,请看下面的示例代码:

class ReorderExample {  
    int a = 0;  
    boolean flag = false;  
  
    public void writer() {  
        a = 1;          // 1  
        flag = true;    // 2  
    }  
  
    public void reader() {  
        if (flag) {            // 3  
            int i = a * a; // 4  
        }  
    }  
}  

flag变量是个标记,用来标识变量a是否已经被写入。这里假设有两个线程A和B,A首先执行write方法,随后B线程接着执行reader()方法。线程B在执行操作4的时候,能否看到线程A在操作1对共享变量的写入吗?

由于操作1和操作2没有数据依赖关系,编译器和处理器可以对这两个操作重排序;同样,操作3和操作4也没有数据依赖关系,编译器和处理器也可以对这两个操作重排序。让我们先来看看,当操作1和操作2重排序的时候,可能会产生什么效果?请看下面的

在这里插入图片描述
如上图所示,操作1和操作2做了重排序。程序执行的时候,线程A首先写标记变量flag,随后随后线程B读了这个变量。由于条件判断为真,线程B将读取变量a,此时变量a还根本没有被线程A写入,在这里多线程程序的语义被重排序破坏了。

下面再让我们看看,当操作3和操作4重排序的时候会产生什么效果(借助这个重排序,可以顺便说明控制依赖性),下面是操作3和操作4重排序后,程序的执行时序图:
在这里插入图片描述
在程序中,操作3和操作4存在控制依赖关系,当代码中存在控制依赖性的时候,会影响程序序列执行的并行度,为此,编译器会采用猜测执行来克服相关性对并行度的影响,以处理器的猜测执行为例,执行线程B的处理器可以提前读取并计算a*a,然后把计算结果保存到一个名为重排序缓冲的硬件缓存中。当接下来的判断条件为真的时候,就把该计算结果写入变量i中。

从图中我们可以看出,猜测执行实质上对操作3和4做了重排序,重排序在这里破坏了多线程程序的语义。

在单线程程序中,对存在控制依赖的操作重排序,不会改变执行结果(这也是as-if-serial语义允许对存在控制依赖的操作进行重排序的原因);但在多线程程序中,对存在控制依赖的操作重排序,可能会改变程序的执行结果。

CPU性能优化之旅

在这里插入图片描述
可以参考
https://zhuanlan.zhihu.com/p/125549632?utm_source=ZHShareTargetIDMore&utm_medium=social&utm_oi=1224314960559403008

内存屏障

什么是内存屏障

内存屏障(Memory Barrier),也被称为内存栅栏或内存屏障指令,是计算机系统中的一种重要概念,用于控制和优化多核处理器和多线程编程的内存访问顺序和可见性

内存屏障的作用

内存屏障的主要作用是确保内存操作的顺序和可见性,以防止出现意外的重排序和数据竞争问题。内存屏障通常在多线程环境中用于以下目的:

保证内存可见性:内存屏障可以确保一个线程对共享变量的修改在另一个线程看到之前完全可见。这有助于避免数据竞争和不一致性问题。

防止重排序:现代处理器通常会对指令进行重排序以提高性能。内存屏障可以防止编译器和处理器对指令重排序,确保指令按照预期的顺序执行。

控制内存操作的顺序:内存屏障允许程序员精确地控制内存操作的执行顺序,以满足特定需求,例如保证写操作先于读操作。

内存屏障类型

1.读屏障(ifence)
将Invalidate Queues中的指令立即处理 并且强制读取cpu的缓存行 执行 ifence指令之后的读操作不会被重排序到ifence指令之前这意味着其他cpu 暴露出来的缓存行对当前cpu可见
2.写屏障(sfence)
会把 store Buffers中修改刷新到本地缓存中 使得其他cpu可以看到这些修改 而且在执行sfence指令之后的写操作不会重排序到 sfence指令之前 这意味着sfence指令之前的写操作全局可见
3.读写屏障(mfence)
保证了 mfence指令执行前后的读写操作的顺序 同时要求执行 mfence指令之后的写操作全局可见 之前的写操作全局可见

volatile详解

什么是volatile

volatile 是Java中的一个关键字,用于声明变量,表示该变量是共享的,多个线程可以同时访问避免出现可见性和指令重排序问题

volatile的作用

可见性(Visibility):volatile 修饰的变量在一个线程中被修改后,会立即将最新的值写回主内存,而其他线程在访问该变量时会从主内存中读取最新的值。这确保了所有线程能够看到同一个共享变量的最新值,从而避免了数据不一致的问题。

禁止指令重排序(Preventing Reordering):volatile 修饰的变量会禁止编译器和处理器对其进行指令重排序,确保了操作的顺序不会被改变。这对于一些需要保持顺序的操作非常重要,例如双重检查锁定模式中的初始化操作。

虽然 volatile 具有可见性和禁止指令重排序的特性,但它并不提供原子性。这意味着虽然读取和写入 volatile 变量是原子的,但复合操作(例如递增)仍然可能不是原子的。因此,如果多个线程同时修改 volatile 变量的值,可能会导致竞态条件。

使用场景

控制变量的可见性,确保所有线程都可以看到更新后的值。
用于标记状态标志,例如线程间的信号传递。
简单的读写操作,而不需要复杂的同步控制。
用于实现一些高效的单例模式。

案例

用于实现双重检查锁定的单例模式,以确保线程安全的懒汉式单例。

public class Singleton {
    private static volatile Singleton instance;

    private Singleton() {
        // 私有构造函数
    }

    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

volatile怎么解决的可见性问题和指令重排序问题

事实上,我们的JMM模型就是类比CPU多核缓存架构的,它的作用是屏蔽掉了底层不同计算机的区别
JMM不是真实存在的,只是一个抽象的概念。volatile也是借助MESI缓存一致性协议和总线嗅探机制及内存屏障才得以完成

volatile关键字会在JVM层面声明一个C++的volatile 他能防止JIT层面的指令重排序
在对修饰了volatile关键字的字段复制后 JVM会调用 storeload()内存屏障方法该方法声明了lock指令 该指令有两个作用
1.在CPU层面赋值的指令会先存储到StoreBuffers中 所以Lock指令会使得StoreBuffers的数据刷新到缓存行
2.使得其他CPU缓存了该字段的缓存行失效 也就是让存储在Invalidate Queues中的对该字段缓存行失效指令立即生效,当其他线程再去读取该字段的值时会先从内存中或者其他缓存了该字段的缓存行中重新读取从而获得最新的值

参考链接

https://zhuanlan.zhihu.com/p/651732241
https://blog.csdn.net/weixin_46410481/article/details/120395904
https://zhuanlan.zhihu.com/p/125549632?utm_source=ZHShareTargetIDMore&utm_medium=social&utm_oi=1224314960559403008

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

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

相关文章

使用Perl和WWW::Mechanize库编写

以下是一个使用Perl和WWW::Mechanize库编写的网络爬虫程序的内容。代码必须使用以下代码:jshk.com.cn/get_proxy 首先,确保已经安装了Perl和WWW::Mechanize库。如果没有,请使用以下命令安装: cpan WWW::Mechanize创建一个新的Pe…

【MATLAB源码-第52期】基于matlab的4用户DS-CDMA误码率仿真,对比不同信道以及不同扩频码。

操作环境: MATLAB 2022a 1、算法描述 1. DS-CDMA系统 DS-CDMA (Direct Sequence Code Division Multiple Access) 是一种多址接入技术,其基本思想是使用伪随机码序列来调制发送信号。DS-CDMA的特点是所有用户在同一频率上同时发送和接收信息&#xf…

抖音热搜榜:探索热门话题的独特魅力

在信息爆炸的时代,我们每天都会接收到大量的信息,而抖音热搜榜就像是一个窗口,让我们能够窥见当下最受欢迎、最具话题性的内容。作为全球最受欢迎的短视频平台之一,抖音凭借其海量的内容、独特的推荐算法,让越来越多的…

阿里低代码Low Code Engine快速上手

一、环境准备 在正式开始之前,我们需要先安装相应的软件:WSL、Node等。Window 环境需要使用 WSL 在 windows 下进行低代码引擎相关的开发。安装教程➡️ WSL 安装教程。对于 Window 环境来说,之后所有需要执行命令的操作都是在 WSL 终端执行的。 2.1 Node 推荐安装Node 1…

Topaz Photo AI forMac/win:革命性的图片降噪软件

Topaz Photo AI是一款革命性的图片降噪软件,它利用人工智能技术对图片进行降噪处理,让你的照片焕然一新。与传统的降噪软件不同,Topaz Photo AI不仅降噪效果更出色,而且操作简单,让你可以轻松地提升图片质量。 Topaz …

CUDA学习笔记6——事件计时

事件计时 CUDA事件是直接在GPU上实现的,因此它们不适用于对同时包含设备代码和主机代码的混合代码计时。 cudaEventCreate 创建一个事件cudaEventRecord 记录一个事件cudaEventElapsedTime 计算两个事件之间经历的时间,第一个参数为某个浮点变量的地址…

MS12_020 3389远程溢出漏洞

1.search ms12_020 搜索ms12_020 2.use auxiliary/scanner/rdp/ms12_020_check 检查是否存在ms12_020漏洞 show options 查看所需参数 set RHOSTS x.x.x.x 设置目标IP地址 run 执行 检测出来有Ms12_020漏洞 3.use auxiliary/dos/windows/rdp/ms12_020_maxchannelids 选择…

SMOS土壤水分产品下载

SMOS土壤水分产品下载 打开下载网站 打开网站 打开DATA下的SMOS 然后找到SMOS的L1和L2data 首先需要注册一下,在该网站 然后找到了SMOS的NRT土壤水分产品,该产品从2010年开始,一直发布到现在,是基于L波段。 这里就是每天的土…

卷积神经网络CNN学习笔记-MaxPool2D函数解析

目录 1.函数签名:2.学习中的疑问3.代码 1.函数签名: torch.nn.MaxPool2d(kernel_size, strideNone, padding0, dilation1, return_indicesFalse, ceil_modeFalse) 2.学习中的疑问 Q:使用MaxPool2D池化时,当卷积核移动到某位置,该卷积核覆盖区域超过了输入尺寸时,MaxPool2D会…

emqx 启动正常,但是1883端口无法telnet,emqx无法正常工作

emqx一直正常工作,后面突然就不工作了,查找日志,发现报错说设备空间不足,但是我记得华为云SSD从40G扩容到500G,不至于空间不足,于是运行df -Dh确实显示只有40G,运行lsblk确实有500G,…

2016-2023全国MPA国家A类线趋势图:浙大MPA要高多少?

公共管理硕士(MPA)项目的国家线这些年相对比较平稳,虽然以体制内考生为主的项目总体应试能力是比较强的,但因为全国mpa项目众多,能力参差不齐,导致每年的国家线划定也需要考虑综合情况。根据专注浙大的杭州…

Qt实现一个电子相册

一、要实现的功能 在窗口中可以显示图片,并且能够通过两个按钮进行图片的前进和后退的顺序切换。有一个按钮,通过这个按钮可以从所存图片资源中随机选取一个图片进行展示通过按钮可以控制图片自动轮播顺序切换的开始与停止,显示当前系统的时…

Spring framework day 03:Spring 整合 Mybatis(分页)

前言 在当今快速发展的软件开发领域,Java作为一种广泛使用的编程语言,以其强大的生态系统和丰富的框架而备受推崇。而在Java开发中,Spring框架几乎成为了事实上的标准,它为开发者提供了一种优雅且高效的方式来构建企业级应用程序…

CVE-2019-0708漏洞实战

使用命令:search 0708搜索exp脚本 搜索网段中主机漏洞 use auxiliary/scanner/rdp/cve_2019_0708_bluekeep 照例,show options 看一下配置 设置网段set RHOSTS x.x.x.x run运行就行了 使用攻击模块 use exploit/windows/rdp/cve_2019_0708_bluekee…

PAM从入门到精通(十八)

接前一篇文章:PAM从入门到精通(十七) 本文参考: 《The Linux-PAM Application Developers Guide》 PAM 的应用开发和内部实现源码分析 先再来重温一下PAM系统架构: 更加形象的形式: 六、整体流程示例 2.…

Java学习笔记(五)——数组、排序和查找

一、数组 数组可以存放多个同一类型的数据。数组也是一种数据类型,是引用类型。即数组就是一组数据。 (一)数组的使用 1、使用方式1——动态初始化 (1)数组的定义: 数据类型 数组名[] new 数据类型…

ubuntu安装rust教程

参考【Rust】Linux上安装Rust开发环境 sudo apt-get install curl# 注意,不开代理很可能下不到,一直报403 export RUSTUP_DIST_SERVERhttps://mirrors.ustc.edu.cn/rust-static export RUSTUP_UPDATE_ROOThttps://mirrors.ustc.edu.cn/rust-static/rustu…

【软考-中级】系统集成项目管理工程师 【19 项目收尾管理】

持续更新。。。。。。。。。。。。。。。 【第十九章】收尾管理 (选择题1分) 19.1 项目验收19.2 项目总结19.3系统维护19.3.1软件项目的后续工作19.3.2系统集成项目的后续工作 19.4 项目后评价1. 信息系统目标评价2. 信息系统过程评价3. 信息系统效益评价…

带温度的softmax

用pytorch写一下使用带有温度的softmax的demo import torch import torch.nn.functional as F# 定义带有温度的softmax函数 def temperature_softmax(logits, temperature1.0):return F.softmax(logits / temperature, dim-1)# 输入logits logits torch.tensor([[1.0, 2.0, 3.…