type
status
date
slug
summary
tags
category
icon
password
 

1、冯诺依曼模型

notion image
冯诺依曼模型定义计算机基本结构为 5 个部分, 分别是中央处理器(CPU)内存输入设备输出设备总线

1.1 CPU

中央处理器也就是我们常说的 CPU,32 位和 64 位 CPU 最主要区别在于一次能计算多少字节数据:
  • 32 位 CPU 一次可以计算 4 个字节
  • 64 位 CPU 一次可以计算 8 个字节
CPU 内部还有一些组件:
  • 控制单元负责控制 CPU 工作
  • 逻辑运算单元负责计算
  • 寄存器存储计算时的数据
而寄存器可以分为多种类,每种寄存器的功能又不尽相同。
  • 通用寄存器,用来存放需要进行运算的数据,比如需要进行加和运算的两个数据。
  • 程序计数器(PC),用来存储 CPU 要执行下一条指令「所在的内存地址」,注意不是存储了下一条要执行的指令,此时指令还在内存中,程序计数器只是存储了下一条指令的地址。
  • 指令寄存器,用来存放程序计数器指向的指令,也就是当前正在执行的指令本身,指令被执行完成之前,指令都存储在这里。

内存

我们的程序和数据都是存储在内存,存储的区域是线性的。
数据存储的单位是一个二进制位(bit),即 0 或 1。最小的存储单位是字节(byte),1 字节等于 8 位。
内存的地址是从 0 开始编号的,然后自增排列,最后一个地址为内存总字节数 - 1,这种结构好似我们程序里的数组,所以内存的读写任何一个数据的速度都是一样的。

总线

总线是用于 CPU 和内存以及其他设备之间的通信,总线可分为 3 种:
  • 地址总线:用于指定 CPU 将要操作的内存地址。
  • 数据总线:用于读写内存的数据。
  • 控制总线:用于发送和接收信号,比如中断、设备复位等信号,CPU 收到信号后自然进行响应,这时也需要控制总线。
当 CPU 要读写内存数据的时候,一般需要通过两个总线:首先要通过「地址总线」来指定内存的地址,再通过「数据总线」来传输数据。

输入输出设备

输入设备向计算机输入数据,计算机经过计算后,把数据输出给输出设备。期间,如果输入设备是键盘, 按下按键时是需要和 CPU 进行交互的,这时就需要用到「控制总线」了。

程序执行

程序实际上是一条一条指令,一个通用 CPU 要完成各类计算、推理、判断和控制工作,它的指令种类少则几十种,多则数百种,CPU的各种指令的集合称为CPU的指令集
指令集决定了处理器的架构,处理器架构就是处理器的硬件架构,称为微架构。是一堆硬件电路,去实现指令集所规定的操作运算。
简单来说,指令集是CPU的顶层设计规范,微架构是这个顶层规范的物理实现,这种实现可以有多种,实现方法可以有变化。一般说的CPU架构包含了CPU指令集和微架构两个东西

指令集类型

指令集分为两种主流类型:
  • CISC(Complex Instruction Set Computers,复杂指令集)
  • RISC(Reduced Instruction Set Computers,精简指令集)
CISC指令集:CISC的指令能力强,指令长度变长,但多数指令使用率低,增加了CPU的复杂度,增加CPU功耗和发热。
  • x86:x86 是典型的CISC指令集,适用于PC机,桌面等设备。
RISC指令集:大部分为单周期指令,指令长度固定,操作寄存器,只有Load/Store操作内存,适用于移动端设备。
  • ARM:最流行的RISC指令集,占移动设备端90%以上的市场。
  • MIPS:MIPS是RISC的开山之作,但是生态没有起来。
  • RISC-V:综合以上特点新开发的,完全开源,但是生态链还在路上。

执行过程

上面我们提到程序执行的过程实际上是执行一条一条指令,负责执行指令的就是 CPU 了。
notion image
  1. 第一步,CPU 读取「程序计数器」的值,这个值是指令的内存地址,然后 CPU 的「控制单元」操作 「地址总线」指定需要访问的内存地址,接着通知内存设备准备数据,数据准备好后通过「数据总 线」将指令数据传给 CPU,CPU 收到内存传来的数据后,将这个指令数据存入到「指令寄存器」。
  1. 第二步,CPU 分析「指令寄存器」中的指令,确定指令的类型和参数,如果是计算类型的指令,就把 指令交给「逻辑运算单元」运算;如果是存储类型的指令,则交由「控制单元」执行。
  1. 第三步,CPU 执行完指令后,「程序计数器」的值自增,表示指向下一条指令。这个自增的大小,由 CPU 的位宽决定,比如 32 位的 CPU,指令是 4 个字节,需要 4 个内存地址存放,因此「程序计数 器」的值会自增 4。

执行周期

指令执行是按照周期来执行的,CPU 的工作就是一个周期接着一个周期,周而复始。
notion image
  1. CPU 通过程序计数器读取对应内存地址的指令,这个部分称为 Fetch(取得指令)。
  1. CPU 对指令进行解码,这个部分称为 Decode(指令译码)。
  1. CPU 执行指令,这个部分称为 Execution(执行指令)。
  1. CPU 将计算结果存回寄存器或者将寄存器的值存入内存,这个部分称为 Store(数据回写)。
对CPU整个生命周期的不同粒度的划分,可以划分为:
  • 时钟周期
  • 机器周期
  • 指令周期
CPU的最小生命单位就是时钟周期,一个机器周期包括若干个时钟周期,一个指令周期包含若干个机器周期。所以按粒度排序,指令周期 > 机器周期 > 时钟周期

时钟周期

时钟周期也称为振荡周期,代表完成一次脉冲信号高低电平的转换,是计算机中最基本的、最小的时间单位。在一个时钟周期内,CPU仅完成一个最基本的动作。
时钟周期定义为1/时钟脉冲频率,比如我们有一个 1 GHz 的 CPU,指的是时钟频率是 1 G,代表着 1 秒 会产生 1G 次数的脉冲信号,每一次脉冲信号高低电平的转换就是一个周期,所以时间周期市场为 1s/1G = 1ms。
在8051单片机中把一个时钟周期定义为一个节拍(用P表示),二个节拍定义为一个状态周期(用S表示)。

机器周期

在计算机中,为了便于管理,常把一条指令的执行过程划分为若干个阶段,每一阶段完成一项工作。例如,取指令、存储器读、存储器写等,这每一项工作称为一个基本操作。完成一个基本操作所需要的时间称为机器周期
一般情况下,一个机器周期由若干个S周期(状态周期)组成。8051系列单片机的一个机器周期同6个S周期(状态周期)组成。前面已说过一个时钟周期定义为一个节拍(用P表示),二个节拍定义为一个状态周期(用S表示),8051单片机的机器周期由6个状态周期组成,也就是说一个机器周期=6个状态周期=12个时钟周期

指令周期

指令周期是执行一条指令所需要的时间,一般由若干个机器周期组成。指令不同,所需的机器周期数也不同。对于一些简单的的单字节指令,在取指令周期中,指令取出到指令寄存器后,立即译码执行,不再需要其它的机器周期。对于一些比较复杂的指令,例如转移指令、乘法指令,则需要两个或者两个以上的机器周期。
通常含一个机器周期的指令称为单周期指令,包含两个机器周期的指令称为双周期指令。

相互关系

notion image

硬中断和软中断

有时候我们向cpu发出一些指令的时候希望得到cpu的快速响应执行。比如我们从键盘上输入文字的时候,肯定是希望屏幕上立刻显示出来,而不是等电脑播放完当前的音乐后再显示出来。这时候我们就需要引入中断机制
中断是系统用来响应硬件设备请求的一种机制,操作系统收到硬件的中断请求,会打断正在执行的进程,然后调用内核中的中断处理程序来响应请求
中断处理程序在响应中断时,会「打断其他进程的运行」,可能还会「临时关闭中断」,系统中其他的中断请求都无法被响应,也就说中断有可能会丢失,所以我们要求中断处理程序尽快执行完。
为了解决中断处理程序执行时长过长的问题,linux将中断过程分成两个阶段:
  • 上半部对应硬中断,由硬件触发中断,会打断 CPU 正在执行的任务,然后立即执行中断处理程序。主要是负责耗时短的工作,特点是快速执行。
  • 下半部对应软中断,由内核触发中断,负责上半部未完成的工作,以内核线程的方式执行,并且每一个 CPU 都对应一个软中断内核线程,名字通常为 「ksoftirqd/CPU 编号」。通常都是耗时比较⻓的事情,特点是延迟执行。
常⻅的网卡接收网络包的例子。网卡收到网络包后,会通过硬件中断通知内核有新的数据到了,于是内核就会调用对应的中断处理程序来 响应该事件,这个事件的处理也是会分成上半部和下半部。
  • 上部分要做到快速处理,所以只要把网卡的数据读到内存中,然后更新一下硬件寄存器的状态,比如把状态更新为表示数据已经读到内存中的状态值。接着内核会触发一个软中断,就可以返回硬中断响应了。
  • 下半部分把一些处理比较耗时且复杂的事情,交给「软中断处理程序」去做,其主要是需要从内存中找到网络数据,再按照网络协议栈,对网络数据进行逐层解析和 处理,最后把数据送给应用程序。

存储器结构

notion image
存储器通常可以分为这么几个级别:
  • 寄存器
  • CPU Cache
    • 1-Cache
    • L2-Cache
    • L3-Cahce
  • 内存
  • SSD/HDD 硬盘

寄存器

寄存器是最靠近 CPU 的控制单元和逻辑计算单元的存储器。存储器的数量通常在几十到几百之间,32 位 CPU 中单个寄存器可以存储 4 个字节,64 位 CPU 中大多数寄存器可以存储 8 个字节,所以寄存器的总大小大概是几百个字节
寄存器的访问速度非常快,一般要求在半个 CPU 时钟周期内完成读写。

CPU Cache

CPU Cache 也存在 CPU 内部,处理速度相比寄存器慢了一点,但是能存储的数据也稍微多了一些。
CPU Cache 用的是一种叫 SRAM(Static Random-Access Memory,静态随机存储器) 的芯片。SRAM 之所以叫「静态」存储器,是因为只要有电,数据就可以保持存在,而一旦断电,数据就会丢失了。
CPU Cache 通常会分为 L1、L2、L3 三层。
notion image
  • L1 高速缓存:访问速度几乎和寄存器一样快,通常只需要 2~4 个时钟周期,大小在几十 KB 到几百 KB 不等,每个 CPU 都有单独的 L1 高速缓存。L1 高速缓存通常分成指令缓存数据缓存。分别存储指令和数据。
  • L2 高速缓存:访问速度为 10~20 个时钟周期,大小在几百 KB 到几 MB 不等,每个 CPU 都有单独的 L2 高速缓存
  • L3 高速缓存:访问速度在 20~60 个时钟周期,大小在几 MB 到几十 MB 不等,多个 CPU 共享相同的 L3 高速缓存。

内存

内存存在 CPU 外部,使用的是 DRAM (Dynamic Random Access Memory,动态随机存取存储器) 的芯片。
DRAM 存储一个 bit 数据,只需要一个晶体管和一个电容就能存储,但是因为数据会被存储在电容里,电 容会不断漏电,所以需要「定时刷新」电容,才能保证数据不会被丢失,这就是 DRAM 之所以被称为「动态」存储器的原因,只有不断刷新,数据才能被存储起来。
DRAM 的访问的速度会更慢,内存速度大概在 200~300 个时钟周期之间。

SSD/HDD硬盘

SSD(Solid-state disk) 就是我们常说的固体硬盘,结构和内存类似,但是它相比内存的优点是断电后数据还是存在的,而内存、寄存器、高速缓存断电后数据都会丢失。内存的读写速度比 SSD 大概快 10~1000 倍。
SSD由主控芯片闪存颗粒缓存芯片三部分组成。
主控芯片
当前主流的主控芯片厂商有 marvell 迈威(俗称“马牌”)、SandForce、siliconmotion慧荣、phison群联、jmicron智微等。
闪存颗粒
闪存(Flash Memory)颗粒本质上是一种长寿命的非易失性(在断电情况下仍能保持所存储的数据信息)的存储器,其中NAND闪存是应用规模最广泛的颗粒。
根据NAND闪存中电子单元密度的差异,又可以分为SLC、MLC、TLC和QLC颗粒。
  • SLC:每个Cell单元存储1bit信息,也就是只有0、1两种电压变化,结构简单,电压控制也快速,写入数据时电压变化区间小,寿命长,读写次数在10万次以上,造价高,多用于企业级高端产品。
  • MLC:每个cell单元存储2bit信息,需要更复杂的电压控制,有00,01,10,11四种变化,寿命长,造价可接受,多用民用高端产品,读写次数在5000左右。
  • TLC:每个cell单元存储3bit信息,电压从000到001有8种变化,容量比MLC再次增加1/3,成本更低,但是架构更复杂,使命寿命低,读写次数在1000~2000左右,是当下主流厂商首选闪存颗粒。
  • QLC:每个cell单元存储4bit信息,电压有16种变化,但是容量能增加33%,就是写入性能、P/E寿命与TLC相比会进一步降低,主要应用于低端市场。
notion image
缓存芯片
缓存芯片在SSD中属于锦上添花的作用,部分低端的SSD出于成本控制可能都没有缓存芯片。
由于固态硬盘内部的磨损机制,就导致固态硬盘在读写小文件和常用文件时,会不断进行数据的整块的写入缓存,然而导出到闪存颗粒,这个过程需要大量缓存维系。特别是在进行大数量级的碎片文件的读写进程,高缓存的作用更是明显。而没有缓存芯片的固态硬盘在用了一段时间后,开始掉速
当然,还有一款传统的硬盘,也就是机械硬盘(Hard Disk Drive, HDD),它是通过物理读写的方式来访问 数据的,因此它访问速度是非常慢的,它的速度比内存慢 10W 倍左右。由于 SSD 的价格快接近机械硬盘了,因此机械硬盘已经逐渐被 SSD 替代了。

相互关系

notion image
当 CPU 需要访问内存中某个数据的时候,如果寄存器有这个数据,CPU 就直接从寄存器取数据即 可,如果寄存器没有这个数据,CPU 就会查询 L1 高速缓存,如果 L1 没有,则查询 L2 高速缓存,L2 还 是没有的话就查询 L3 高速缓存,L3 依然没有的话,才去内存中取数据。

一致性问题

CPU Cache Line

CPU Cache 的数据是从内存中读取过来的,它是以一小块一小块读取数据的,每一小块称为 Cache Line(缓存块)。CPU Line 是 CPU 从内存读取数据的基本单位。一般来说,L1 Cache Line 大小是 64 字节,也就意味着 L1 Cache 一次载入数据的大小是 64 字节
比如,有一个 int array[100] 的数组,当载入 array[0] 时,由于这个数组元素的大小在内存只占 4 字节,不足 64 字节,CPU 就会顺序加载数组元素到 array[15] ,意味着 array[0]~array[15] 数组元素都会被缓存在 CPU Cache 中了,因此当下次访问这些数组元素时,会直接从 CPU Cache 读取,而不用再从内存中读取。
每一行 CPU Line 都和一块内存地址形成映射。CPU 要根据内存地址读取数据的时候,无论数据是否存放到 Cache 中,CPU 都是先访问 Cache,只有当 Cache 中找不到数据时,才会去访问内存,并把内存中的数据读入到 Cache 中,CPU 再从 CPU Cache 读取数据。
CPU 在从 CPU Cache 读取数据的时候,并不是读取 CPU Line 中的整个数据块,而是读取 CPU 所需要的一个数据片段,这样的数据统称为一个字(Word)。CPU 通过包括组标记CPU Line 索引偏移量这三种信息,在 CPU Cache Line 中查找需要的字。而对于 CPU Cache 里的数据结构,则是由索引 + 有效位 + 组标记 + 数据块组成。
notion image
如果内存中的数据已经在 CPU Cache 中了,那 CPU 访问一个内存地址的时候,会经历这 4 个步骤:
  1. 根据内存地址中索引信息,计算在 CPU Cahe 中的索引,也就是找出对应的 CPU Line 的地址。
  1. 找到对应 CPU Line 后,判断 CPU Line 中的有效位,确认 CPU Line 中数据是否是有效的,如果是无效的,CPU 就会直接访问内存,并重新加载数据,如果数据有效,则往下执行。
  1. 对比内存地址中组标记和 CPU Line 中的组标记,确认 CPU Line 中的数据是我们要访问的内存数据,如果不是的话,CPU 就会直接访问内存,并重新加载数据,如果是的话,则往下执行。
  1. 根据内存地址中偏移量信息,从 CPU Line 的数据块中,读取对应的字。

写回策略

当我们更新了 CPU Cache Line 的内容时,需要在适当的时机把更新的内容写回内存。根据更新的时机,可以分为:
  • 写直达(Write Through):把数据同时写入内存和 Cache 中。安全性高,但性能较差。
  • 写回(Write Back):当发生写操作时,只有在缓存不命中,同时数据对应的 Cache 中 的 Cache Block 为脏标记的情况下,才会将数据写到内存中;而在缓存命中的情况下,则在写入后 Cache 后,只需把该数据对应的 Cache Block 标记为脏即可,而不用写到内存里。减少了数据写回内存的频率,这样便可以提高系统的性能。

缓存一致性问题

现代 CPU 都是多核的,每个核心都会有单独的 L1/L2 缓存,同一块内存地址的数据可能会被读取到不同个核心的 CPU Cache Line 上,如果不能处理好 CPU Cache Line 上的数据写回内存的时机,容易发生缓存一致性问题
假设 A 号核心和 B 号核心同时运行两个线程,都操作共同的变量 i(初始值为 0 )。
notion image
  1. A 号核心执行了 i++ 语句的时候,为了考虑性能,使用了我们前面所说的写回策略,先把值为 1 的执行结果写入到 L1/L2 Cache 中,然后把 L1/L2 Cache 中对应的 Block 标记为脏的,这个时候数据其实没有被同步到内存中的,因为写回策略,只有在 A 号核心中的这个 Cache Block 要被替换的时候,数据才会写入到内存里。
  1. B 号核心尝试从内存读取 i 变量的值,则读到的将会是错误的值,因为刚才 A 号核心更新 i 值还没写入到内存中,内存中的值还依然是 0。
这个就是所谓的缓存一致性问题。要解决这一问题,就需要一种机制,来同步两个不同核心里面的缓存数据。要实现的这个机制的话,要保证做到下面这 2 点:
  • 某个 CPU 核心里的 Cache 数据更新时,必须要传播到其他核心的 Cache,这个称为写传播 (Wreite Propagation)。
  • 某个 CPU 核心里对数据的操作顺序,必须在其他核心看起来顺序是一样的,这个称为事务的串形化(Transaction Serialization)。

MESI协议

早期有一种总线嗅探(Bus Snooping)的技术来保证写传播:
  • 当 A 号 CPU 核心修改了 L1 Cache 中 i 变量的值,通过总线把这个事件广播通知给其他所有的核心,然后每个 CPU 核心都会监听总线上的广播事件,并 检查是否有相同的数据在自己的 L1 Cache 里面,如果 B 号 CPU 核心的 L1 Cache 中有该数据,那么也需要把该数据更新到自己的 L1 Cache。
总线嗅探的原理非常简单,但是缺点也很明显:
  • CPU 需要每时每刻监听总线上的一切活动,但是不管别的核心的 Cache 是否缓存相同的数据,都需要发出一个广播事件,这无疑会加重总线的负载。
  • 只是保证了某个 CPU 核心的 Cache 更新数据这个事件能被其他 CPU 核心知道,但是并不能保证事务串形化。
MESI 协议基于总线嗅探机制实现了事务串形化,也用状态机机制降低了总线带宽压力,实现了 CPU 缓存一致性。
MESI 协议把 Cache Line 标记为 4 种不同的状态,MESI 其实是 4 个状态单词的开头字母缩写:
  • Modified,已修改,代表该 Cache Block 上的数据已经被更新过,但是还没有写 到内存里。
  • Exclusive,独占,代表 Cache Block 里的数据是干净的,且数据只存储在一个 CPU 核心的 Cache 里,而其他 CPU 核心的 Cache 没有该数据。
  • Shared,共享,代表 Cache Block 里的数据是干净的,且相同的数据在多个 CPU 核心的 Cache 里都有。
  • Invalidated,已失效,代表 Cache Block 里的数据已经失效了,不可以读取该状态的数据。
「独占」和「共享」的数据都是干净的,差别在于:
  • 「独占」状态表示数据只存储在一个 CPU 核心的 Cache 里,而其他 CPU 核心的 Cache 没有该数据。这个时候,如果要向独占的 Cache 写数据,就可以直接自由地写入,而 不需要通知其他 CPU 核心,因为只有你这有这个数据,就不存在缓存一致性的问题了,于是就可以随便操作该数据。在「独占」状态下的数据,如果有其他核心从内存读取了相同的数据到各自的 Cache ,那么这个时 候,独占状态下的数据就会变成共享状态。
  • 「共享」状态代表着相同的数据在多个 CPU 核心的 Cache 里都有。所以当我们要更新 Cache 里面的数据的时候,不能直接修改,而是要先向所有的其他 CPU 核心广播一个请求,要求先把其他核心的 Cache 中对应的 Cache Line 标记为「无效」状态,然后再更新当前 Cache 里面的数据。
事实上,整个 MESI 的状态可以用一个有限状态机来表示它的状态流转。还有一点,对于不同状态触发的事件操作,可能是来自本地 CPU 核心发出的广播事件,也可以是来自其他 CPU 核心通过总线发出的广播事件。下图即是 MESI 协议的状态图:
notion image
notion image
可以发现当 Cache Line 状态是「已修改」或者「独占」状态时,修改更新其数据不需要发送广播给其他 CPU 核心,这在一定程度上减少了总线带宽压力。

伪共享问题

上面我们提高 MESI 协议,解决了同一个变量被读取到不同 CPU 的 CPU Cache Line 的一致性问题
如果两个不同的变量被读取到两个不同 CPU 的 CPU Cache Line,每个 CPU 只读写其 Cache 上的单个变量,理论上,两个变量各自的执行应该没有任何关系,互相也不会受到影响。但是如果使用了 MESI 协议,就会引发新的问题:伪共享问题
notion image
  • 1 号核心读取变量 A,由于 CPU 从内存读取数据到 Cache 的单位是 Cache Line,也正好变量 A 和 变 量 B 的数据归属于同一个 Cache Line,所以 A 和 B 的数据都会被加载到 Cache,并将此 Cache Line 标 记为「独占」状态。
notion image
  • 接着,2 号核心开始从内存里读取变量 B,同样的也是读取 Cache Line 大小的数据到 Cache 中,此 Cache Line 中的数据也包含了变量 A 和 变量 B,此时 1 号和 2 号核心的 Cache Line 状态变为「共享」状 态。
notion image
  • 1 号核心需要修改变量 A,发现此 Cache Line 的状态是「共享」状态,所以先需要通过总线发送消息 给 2 号核心,通知 2 号核心把 Cache 中对应的 Cache Line 标记为「已失效」状态,然后 1 号核心对应的 Cache Line 状态变成「已修改」状态,并且修改变量 A。
notion image
  • 2 号核心需要修改变量 B,此时 2 号核心的 Cache 中对应的 Cache Line 是已失效状态,另外由 于 1 号核心的 Cache 也有此相同的数据,且状态为「已修改」状态,所以要先把 1 号核心的 Cache 对应 的 Cache Line 写回到内存,然后 2 号核心再从内存读取 Cache Line 大小的数据到 Cache 中,最后把变 量 B 修改到 2 号核心的 Cache 中,并将状态标记为「已修改」状态。
可以发现如果 1 号和 2 号 CPU 核心这样持续交替的分别修改变量 A 和 B,Cache 并没有起到缓存的效果,虽然变量 A 和 B 之间其实并没有任何的关系,但是因为同时归属 于一个 Cache Line ,这个 Cache Line 中的任意数据被修改后,都会相互影响。这种因为多个线程同时读写同一个 Cache Line 的不同变量时,而导致 CPU Cache 失效的现象称为 伪共享(False Sharing)
避免伪共享
  1. 使用Linux 内核中的 __cacheline_aligned_in_smp 宏定义。
notion image
notion image
  1. 手动「字节填充 + 继承」。例如 Java 并发框架 Disruptor 里面的 RingBuffer 类。
notion image
  • RingBufferPad 定义了 p1 ~ p7 7 个 long 类型的变量。
  • RingBufferFields 继承了 RingBufferPad,里面定义的全是 final 类型的字段。
  • RingBuffer 继承了RingBufferFields,再次定义了 p1 ~ p7 7 个 long 类型的变量。
CPU Cache 从内存读取数据的单位是 CPU Line,一般 64 位 CPU 的 CPU Line 的大小是 64 个字节,一个 long 类型的数据是 8 个字节,所以 CPU 一下会加载 8 个 long 类型的数据。
根据 JVM 对象继承关系中父类成员和子类成员,内存地址是连续排列布局的,因此 RingBufferPad 中的 7 个 long 类型数据作为 Cache Line 前置填充,而 RingBuffer 中的 7 个 long 类型数据则作为 Cache Line 后置填充,这 14 个 long 变量没有任何实际用途,更不会对它们进行读写操作。
而且 RingBufferFelds 里面定义的这些变量都是 final 修饰的,意味着第一次加载之后不会再修改, 又 由于「前后」各填充了 7 个不会被读写的 long 类型变量,所以无论怎么加载 Cache Line,这整个 Cache Line 里都没有会发生更新操作的数据,于是只要数据被频繁地读取访问,就自然没有数据被换出 Cache 的可能,也因此不会产生伪共享的问题。
notion image
Linux系列:内存管理Elasticsearch系列:索引配置(Setting)
mcbilla
mcbilla
一个普通的干饭人🍚
Announcement
type
status
date
slug
summary
tags
category
icon
password
🎉欢迎来到飙戈的博客🎉
-- 感谢您的支持 ---
👏欢迎学习交流👏