java八股文面试[多线程]——指令重排序

关于a的操作,由原来的6个指令,变成了4个指令。

1. 指令重排序的介绍
1)指令重排序的类型
在执行程序时为了提高性能,编译器和处理器常常会对指令做重排序。

重排序分三种类型:
编译器优化的重排序 编译器在不改变单线程程序语义的前提下(代码中不包含synchronized关键字),可以重新安排语句的执行顺序。
指令级并行的重排序 现代处理器采用了指令级并行技术(Instruction-Level Parallelism, ILP)来将多条指令重叠执行。如果不存在数据依赖性,处理器可以改变语句对应机器指令的执行顺序。
内存系统的重排序 由于处理器使用缓存和读/写缓冲区,这使得加载和存储操作看上去可能是在乱序执行


重排序又可以分为两大类

一是编译器重排序:编译器优化重排序,

二是处理器重排序:指令级并行重排序、内存系统重排序。


2)重排序的流程
从java源代码到最终实际执行的指令序列,会分别经历下面三种重排序:

在这里插入图片描述


3)重排序意义
处理器为提高运算速度而做出违背代码原有顺序的优化


4)存在的问题
这三种重排序方法都可能会导致多线程程序出现内存可见性问题

2. 内存系统重排序
1)内存系统的读操作
为提高从内存读取的效率,对读操作进行优化,将其称为程序乱序执行优化。CPU为了提高指令执行效率,会在一条指令执行过程中(比如去内存读数据(慢100倍)),去同时执行另一条指令,前提是,两条指令必须是独立的、没有依赖关系,cpu一般是依赖指令间的内存引用关系来判断的指令间的”独立关系”。


2)内存系统的写操作
写操作的优化称为合并写入技术。现代的处理器使用写缓冲区来临时保存向内存写入的数据。
写操作优化的过程:乱序当cpu执行存储指令时,它会首先试图将数据写到离cpu最近的L1_cache, 如果此时cpu出现L1未命中,则会访问下一级缓存。速度上L1_cache基本能和cpu持平,其他的均明显低于cpu,L2_cache的速度大约比cpu慢20-30倍,而且还存在L2_cache不命中的情况,又需要更多的周期去主存读取。其实在L1_cache未命中以后,cpu就会使用一个另外的缓冲区,叫做合并写存储缓冲区。这一技术称为合并写入技术。


在请求L2_cache缓存行的所有权尚未完成时,cpu会把待写入的数据写入到合并写存储缓冲区,该缓冲区大小和一个cache line大小,一般都是64字节。这个缓冲区允许cpu在写入或者读取该缓冲区数据的同时继续执行其他指令,这就缓解了cpu写数据时cache miss时的性能影响。当后续的写操作需要修改相同的缓存行时,在将后续的写操作提交到L2缓存之前,可以进行缓冲区写合并。

这些64字节的缓冲区维护了一个64位的字段,每更新一个字节就会设置对应的位,来表示将缓冲区交换到外部缓存时哪些数据是有效的。当然,如果程序读取已被写入到该缓冲区的某些数据,那么在读取缓存数据之前会先去读取本缓冲区的。经过上述步骤后,缓冲区的数据还是会在某个延时的时刻更新到外部的缓存(L2_cache).如果能在缓冲区传输到缓存之前将其尽可能填满,这样的效果就会提高各级传输总线的效率,以提高程序性能。


3)写缓冲区造成的优势
优势1,合并写缓冲区中对同一内存地址的多次写操作,可以减少对内存总线的占用;

优势2,通过以批处理的方式刷新写缓冲区;

优势3,写缓冲区可以保证指令流水线持续运行,避免由于处理器停顿下来等待向内存写入数据而产生的延迟。


4)出现问题的原因
单核时代处理器做出的优化可以保证执行结果不会远离预期目标,但是,在多核时代却并非如此。在多核时代,同时会有多个核同时执行指令,每一个核的指令都可能被乱序。另外,处理器还引入了L1,L2,…,Ln等多级缓存机制,每个核心都有自己的缓存机制,这样就导致了逻辑次序上后写入内存的数据未必真的最后写入。最后就带来一个问题,如果不做任何防护措施,处理器最终得出的结果和逻辑得出结果会大不相同。
比如,在一个核上执行写入操作,并在最后写一个标记用来表示操作完毕,之后从另外一个核上通过判断这个标记来判定所需要的数据是否已经就绪,这种做法就存在一定风险:标记位先被写入但之前的操作却并未完成(可能是未计算完成,也可能是数据没有从处理器缓存刷新到主存中,最终导致另外的核使用了错误的数据)。

3. 内存屏障
3.1. 硬件内存屏障
1)CPU内存屏障(硬件内存屏障)
功能:为了解决代码的乱序执行问题,在CPU级别上引入了内存屏障,这里跟JAVA的内存屏障不是一个问题
分类:
指令 功能
sfence 在sfence指令前的写操作必须在sfence指令后的写操作之前完成
lfence 在lfence指令前的读操作必须在lfence指令后的读操作之前完成
mfence 在mfence指令前的读写操作必须在mfence指令后的读写操作之前完成
原子指令:例如X86中的lock指令,执行时会锁住内存子系统来保障执行顺序不改变,甚至能跨多个CPU。
Software Locks通常使用内存屏障原子指令来实现变量的可见性和执行顺序不变

3.2. 内存系统的内存屏障
1)使用背景
对于编译器,JMM的编译器重排序规则会禁止特定类型的编译器重排序(不是所有的编译器重排序都要禁止)。对于处理器重排序,JMM的处理器重排序规则会要求java编译器在生成指令序列时,插入特定类型的内存屏障(memory barriers,intel称之为memory fence)指令,通过内存屏障指令来禁止特定类型的处理器重排序(不是所有的处理器重排序都要禁止)。


2)基本使用
内存屏障(Memory Barrier,或有时叫做内存栅栏,Memory Fence)是一种CPU指令,用于控制特定条件下的重排序和内存可见性问题。Java编译器也会根据内存屏障的规则禁止重排序。java编译器在生成指令序列的适当位置会插入内存屏障指令来禁止特定类型的处理器重排序。

JMM把内存屏障指令分为下列四类:
LoadLoad屏障 语句:Load1; LoadLoad; Load2
在Load2及后续读取操作要在读取的数据被访问前,保证Load1要读取的数据被读取完毕
StoreStore屏障 语句:Store1; StoreStore; Store2
在Store2及后续写入操作执行前,保证Store1的写入操作对其他处理器可见
LoadStore屏障 语句:Load1; LoadStore; Store2
在Store2及后续写入操作被刷出前,保证Load1要读取的数据被读取完毕
StoreLoad屏障 语句:Store1; StoreLoad; Load2
在Load2及后续所有读取操作执行前,保证Store1的写入对其他处理器是可见

3)StoreLoad Barriers是一个“全能型”的屏障,它同时具有其他三个屏障的效果。现代的多处理器大都支持该屏障(其他类型的屏障不一定被所有处理器支持)。执行该屏障开销会很昂贵,因为当前处理器通常要把写缓冲区中的数据全部刷新到内存中(buffer fully flush)


4)允许指令重排序的列表
下面是常见处理器允许的重排序类型的列表:

在这里插入图片描述
注:上表单元格中的“N”表示处理器不允许两个操作重排序,“Y”表示允许重排序。
从上表我们可以看出:常见的处理器都允许Store-Load重排序;常见的处理器都不允许对存在数据依赖的操作做重排序。sparc-TSO和x86拥有相对较强的处理器内存模型,它们仅允许对写-读操作做重排序(因为它们都使用了写缓冲区)。

4. happens-before关系
1)基本介绍
JSR-133使用happens-before的概念来阐述操作之间的内存可见性。在JMM中,如果一个操作执行的结果需要对另一个操作可见,那么这两个操作之间必须要存在happens-before关系。这里提到的两个操作既可以是在一个线程之内,也可以是在不同线程之间


2)happens-before规则
与程序员密切相关的happens-before规则如下:
程序顺序规则 一个线程中的每个操作,happens- before 于该线程中的任意后续操作
监视器锁规则 对一个监视器锁的解锁,happens- before 于随后对这个监视器锁的加锁
volatile变量规则 对一个volatile域的写,happens- before 于任意后续对这个volatile域的读
传递性 如果A happens- before B,且B happens- before C,那么A happens- before C
Start规则
Join规则
特殊说明:两个操作之间具有happens-before关系,并不意味着前一个操作必须要在后一个操作之前执行!happens-before仅仅要求前一个操作(执行的结果)对后一个操作可见,且前一个操作按顺序排在第二个操作之前。


3)happens-before与JMM的关系

在这里插入图片描述
如上图所示,一个happens-before规则通常对应于多个编译器处理器重排序规则。对于java程序员来说,happens-before规则简单易懂,避免java程序员为了理解JMM提供的内存可见性保证而去学习复杂的重排序规则以及这些规则的具体实现。

3.5. as-if-serial语义
1)数据依赖性
如果两个操作访问同一个变量,且这两个操作中有一个为写操作,此时这两个操作之间就存在数据依赖性。数据依赖分下列三种类型:

在这里插入图片描述
上面三种情况,只要重排序两个操作的执行顺序,程序的执行结果将会被改变
注:这里所说的数据依赖性仅针对单个处理器中执行的指令序列和单个线程中执行的操作,不同处理器之间和不同线程之间的数据依赖性不被编译器和处理器考虑。


2)as-if-serial语义
不管怎么重排序(编译器和处理器为了提高并行度),(单线程)程序的执行结果不能被改变,编译器,runtime 和处理器都必须遵守as-if-serial语义。即编译器和处理器在重排序时,会遵守数据依赖性,编译器和处理器不会改变存在数据依赖关系的两个操作的执行顺序。
为了遵守as-if-serial语义,编译器和处理器不会对存在数据依赖关系的操作做重排序,因为这种重排序会改变执行结果。但是,如果操作之间不存在数据依赖关系,这些操作可能被编译器和处理器重排序。
as-if-serial语义把单线程程序保护了起来,遵守as-if-serial语义的编译器,runtime 和处理器共同为编写单线程程序的程序员创建了一个幻觉:单线程程序是按程序的顺序来执行的。as-if-serial语义使单线程程序员无需担心重排序会干扰他们,也无需担心内存可见性问题。

 

 参考:

【23版面试突击】你知道什么是指令重排序?为什么要重排序?_哔哩哔哩_bilibili

指令重排序_Waiting_Mr_Liu的博客-CSDN博客

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

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

相关文章

分布式一致性协议

在对一个分布式系统进行架构设计的过程中,往往会在系统的可用性和数据一致性之间进行反复的权衡,于是就产生了一系列的一致性协议。为了解决分布式一致性问题,在长期的探索研究过程中,涌现出了一大批经典的一致性协议和算法&#…

SpringMVC 第二天

第 1 章 ModelAttribute 和 SessionAttribute[ 应 用 ] 1.1ModelAttribute 1.1.1 使用说明 作用: 该注解是 SpringMVC4.3 版本以后新加入的。它可以用于修饰方法和参数。 出现在方法上,表示当前方法会在控制器的方法执行之前,先执行…

Open3D 进阶(7)Kmeans++点云聚类

目录 一、算法原理1、原理概述2、实现流程3、参考文献二、代码实现三、结果展示四、测试数据本文由CSDN点云侠原创,原文链接。如果你不是在点云侠的博客中看到该文章,那么此处便是不要脸的爬虫。 一、算法原理 1、原理概述

【Android-Flutter】我的Flutter开发之旅

目录: 0、文档:1、在Windows上搭建Flutter开发环境(1)[使用中国镜像(❌详细看官方文档)](https://docs.flutter.dev/community/china)(2)[下载最新版Flutter SDK(已包含Dart)](https://docs.flu…

[SQLITE_ERROR] SQL error or missing database (near “=“: syntax error)【已解决】

这个报的错误是语法错误,但是我并没有看出来这行代码有什么错。 通过排除掉下边两个问题解决的 从增加记录方法复制的下来的代码,只删除了关闭自动提交事务,但是connection.commit忘记删除executeQuery和executeUpdate方法的用法忘记了&…

什么是网络取证(Network Forensics)

企业采用新技术来检查其网络安全是否存在零日漏洞,与立即指示问题的物理层不同,黑客攻击尝试可能会被忽视并变得严重,直到对网络流量有一个整体的可见性。通过实时监控来跟踪其源和目标的流量,以查明问题或潜在问题的根源。 什么…

Redis之集群模式

一、Redis集群 一个节点就是一个运行在集群模式下的Redis服务器,Redis服务器在启动时会根据cluster-enabled配置选项是否为yes来决定是否开启服务器的集群模式。 Redis节点不会互相发现,连接各个节点的工作需要使用cluster meet命令来完成 CLUSTER MEE…

Postman API测试之道:不止于点击,更在于策略

引言:API测试的重要性 在当今的软件开发中,API已经成为了一个不可或缺的部分。它们是软件组件之间交互的桥梁,确保数据的流动和功能的实现。因此,对API的测试显得尤为重要,它不仅关乎功能的正确性,还涉及到…

Android屏幕适配(5) — 最小宽度smallWidth适配

概述 最小宽度smallWidth适配实现屏幕适配方案 详细 前言 在之前的文章中,我们讲到了Android屏幕适配的一些知识,大家感兴趣的话可参考Android屏幕适配(1) — 概念解释Android屏幕适配(2) — drawable与mipmapAndroid屏幕适配(3) — 资源文件夹命名与…

C++元编程——深度双向RNN实验

使用C的标准库实现了双向RNN的功能。最近对DRNN做了一些改进&#xff0c;同时进行了实验&#xff0c;首先DRNN的代码如下&#xff1a; #ifndef _RNN_HPP_ #define _RNN_HPP_ #include <stdio.h> #include <stdlib.h> #include <vector> #include "mat.…

爬虫逆向实战(二十五)--某矿采购公告

一、数据接口分析 主页地址&#xff1a;某矿 1、抓包 通过抓包可以发现数据接口是cgxj/by-lx-page 2、判断是否有加密参数 请求参数是否加密&#xff1f; 通过查看“载荷”模块可以发现有一个param的加密参数 请求头是否加密&#xff1f; 无响应是否加密&#xff1f; 无c…

网关认证的技术方案

我们认证授权使用springsecurity 和oauth2技术尽心实现具体实现流程见第五章文档&#xff0c;这里就是记录一下我们的技术方案 这是最开始的技术方案&#xff0c;我们通过认证为服务获取令牌然后使用令牌访问微服务&#xff0c;微服务解析令牌即可。但是缺点就是每个微服务都要…

TIA博途_更新或修改程序时,如何避免数据块中的参数丢失?

TIA博途_更新或修改程序时,如何避免数据块中的参数丢失? DB 快照功能 可以通过捕获 DB 块变量实际值快照用于恢复值操作,捕获的实际快照值可以复制到 CPU 中的实际值中,也可以用于替换变量的起始值。 通过快照能解决以下场景的问题: • 在 HMI 中设置了很多工艺参数,担心…

1.1 数据库系统简介

思维导图&#xff1a; 1.1.数据库系统简介 前言&#xff1a; 数据库系统是一个软件系统&#xff0c;用于管理和操作数据库。它提供了一个组织良好、高效并能够方便存取的数据存储机制&#xff0c;并且能够支持各种数据操作、事务管理、并发控制和恢复功能。以下是数据库系统的…

9个至少提升50%效率的VSCODE插件

在开始编码之前&#xff0c;您首先需要让您的工作流程适合您。让它更高效、更漂亮、更可定制。它会节省您大量的时间和精力&#xff0c;因此您将有更多的能力进行编码。 无论您是前端还是后端开发人员还是高级 Java 程序员&#xff0c;都没关系。这篇文章对你们大多数人来说仍然…

el-form的表单验证,有值却报红!

正确的写法是 el-form中的form用 :model绑定&#xff0c;各个输入项用 v-model绑定值 原因 显然&#xff0c;区别就是 v-model 和 :model的区别 V-mode v-model是一个语法糖&#xff0c;用于 “表单元素上” 实现双向数据绑定。包括数据绑定和事件监听。 <input v-model&q…

高职教育应对ChatGPT应用的策略

一、完善顶层设计&#xff0c;提升技术水平 在推广ChatGPT平台的过程中&#xff0c;高职院校需要关注技术本身的问题。这就需要在国家和地方政府的引导下&#xff0c;引入更完善的技术顶层设计&#xff0c;提高人工智能在高职教育中的运用水平。具体来说&#xff0c;一方面需要…

Maven之hibernate-validator 高版本问题

hibernate-validator 高版本问题 hibernate-validator 的高版本&#xff08;邮箱注解&#xff09;依赖于高版本的 el-api&#xff0c;tomcat 8 的 el-api 是 3.0&#xff0c;满足需要。但是 tomcat 7 的 el-api 只有 2.2&#xff0c;不满足其要求。 解决办法有 2 种&#xff…

lenovo联想笔记本小新Air-15 2021款Intel版ITL版(82GM)原装出厂Win10系统

自带所有驱动、出厂主题壁纸LOGO、Office办公软件、联想电脑管家等预装程序 所需要工具&#xff1a;16G或以上的U盘 文件格式&#xff1a;ISO 文件大小&#xff1a;11.2GB 链接&#xff1a;https://pan.baidu.com/s/12NTMOt5eUjOIsbayXPyiww?pwdrs4v 提取码&#xf…

证明arcsinx+arccosx=π/2,并且为什么arcsinx-arccosx=π/2不成立

下面我们先直接用代数式来证明一下&#xff1a; 设 y 1 arcsin ⁡ x &#xff0c; y 2 arccos ⁡ x &#xff0c;求 y 1 y 2 由于 x sin ⁡ y 1 cos ⁡ y 2 &#xff0c;而 cos ⁡ y 2 sin ⁡ ( y 2 π 2 ) 那么就得到 y 1 y 2 π 2 &#xff0c;即 y 1 − y 2 π 2 …