啃碎并发(10):内存模型之内部原理

前言

如上一篇文章所述,Java内存模型规范了Java虚拟机与计算机内存是如何协同工作的。Java虚拟机是一个完整计算机的模型,因此,这个模型自然会包含一个内存模型—又称为Java内存模型。

如果你想设计表现良好的并发程序,理解Java内存模型是非常重要的。Java内存模型规定了如何和何时可以看到由其他线程修改过后的共享变量的值,以及在必须时如何同步的访问共享变量

1 Java内存模型

我们先来看看Java 线程运行内存示意图,如下图所示:

这张图告诉我们在线程运行的时候有一个内存专用的一小块内存,当Java程序会将变量同步到线程所在的内存,这时候会操作工作内存中的变量,而线程中变量的值何时同步回主内存是不可预期的

因此,依据上面图的线程运行内存示意图,Java内存模型在JVM内部抽象划分为线程栈和堆。如下图所示:

1.1 线程栈与堆

每一个运行在Java虚拟机里的线程都拥有自己的线程栈。这个线程栈包含了 线程调用的方法当前执行点相关的信息,同时线程栈具有如下特性:

1.一个线程仅能访问自己的线程栈;
2.一个线程创建的本地变量对其它线程不可见,仅自己可见;

即使两个线程执行同样的代码,这两个线程任然在在自己的线程栈中的代码来创建本地变量。因此,每个线程拥有每个本地变量的独有版本

所有原始类型的本地变量都存放在线程栈上,因此对其它线程不可见。一个线程可能向另一个线程传递一个原始类型变量的拷贝,但是它不能共享这个原始类型变量自身。

堆上包含在Java程序中创建的所有对象,无论是哪一个对象创建的。这包括原始类型的对象版本。如果一个对象被创建然后赋值给一个局部变量,或者用来作为另一个对象的成员变量,这个对象任然是存放在堆上

所以,调用栈和本地变量存放在线程栈上,对象存放在堆上,如下图所示:

1.一个本地变量可能是原始类型,在这种情况下,它总是“呆在”线程栈上
2.一个本地变量也可能是指向一个对象的一个引用。在这种情况下,引用(这个本地变量)存放在线程栈上,但是对象本身存放在堆上
3.一个对象可能包含方法,这些方法可能包含本地变量。这些本地变量任然存放在线程栈上,即使这些方法所属的对象存放在堆上
4.一个对象的成员变量可能随着这个对象自身存放在堆上。不管这个成员变量是原始类型还是引用类型。
5.静态成员变量跟随着类定义一起也存放在堆上

存放在堆上的对象可以被所有持有对这个对象引用的线程访问。当一个线程可以访问一个对象时,它也可以访问这个对象的成员变量。如果两个线程同时调用同一个对象上的同一个方法,它们将会都访问这个对象的成员变量,但是每一个线程都拥有这个本地变量的私有拷贝。

上面说到的几点,如下图所示:

1.2 CPU与内存

众所周知,CPU是计算机的大脑,它负责执行程序的指令。内存负责存数据,包括程序自身数据。同样大家都知道,内存比CPU慢很多,现在获取内存中的一条数据大概需要200多个CPU周期(CPU cycles),而CPU寄存器一般情况下1个CPU周期就够了。下面是CPU Cache的简单示意图:

随着多核的发展,CPU Cache分成了三个级别:L1,L2,L3。级别越小越接近CPU,所以速度也更快,同时也代表着容量越小。

1.L1是最接近CPU的,它容量最小,例如32K,速度最快,每个核上都有一个L1 Cache(准确地说每个核上有两个L1 Cache, 一个存数据 L1d Cache, 一个存指令 L1i Cache)。
2.L2 Cache 更大一些,例如256K,速度要慢一些,一般情况下每个核上都有一个独立的L2 Cache。
3.L3 Cache是三级缓存中最大的一级,例如12MB,同时也是最慢的一级, 在同一个CPU插槽之间的核共享一个L3 Cache。

从CPU到大约需要的CPU周期大约需要的时间(单位ns)
寄存器1 cycle
L1 Cache~3-4 cycles~0.5-1 ns
L2 Cache~10-20 cycles~3-7 ns
L3 Cache~40-45 cycles~15 ns
跨槽传输~20 ns
内存~120-240 cycles~60-120ns

在Linux下面用 cat /proc/cpuinfo,或Ubuntu下 lscpu 看看自己机器的缓存情况,更细的可以通过以下命令看看:

$ cat /sys/devices/system/cpu/cpu0/cache/index0/size
32K
$ cat /sys/devices/system/cpu/cpu0/cache/index0/type
Data
$ cat /sys/devices/system/cpu/cpu0/cache/index0/level
1
$ cat /sys/devices/system/cpu/cpu3/cache/index3/level
3 

就像数据库cache一样,获取数据时首先会在最快的cache中找数据,如果没有命中(Cache miss) 则往下一级找,直到三层Cache都找不到,那只要向内存要数据了。一次次地未命中,代表获取数据消耗的时间越长。

同时,为了高效地存取缓存,不是简单随意地将单条数据写入缓存的。缓存是由缓存行组成的,典型的一行是64字节。可以通过下面的shell命令,查看 cherency_line_size 就知道知道机器的缓存行是多大:

$ cat /sys/devices/system/cpu/cpu0/cache/index0/coherency_line_size
64 

CPU存取缓存都是以“行”为最小单位操作的。比如:一个Java long型占8字节,所以从一条缓存行上你可以获取到8个long型变量。所以如果你访问一个long型数组,当有一个long被加载到cache中, 你将无消耗地加载了另外7个。所以你可以非常快地遍历数组。

2 缓存一致性

由于CPU和主存的处理速度上存在一定差别,为了匹配这种差距,提升计算机能力,人们在CPU和主存之间增加了多层高速缓存。每个CPU会有L1、L2甚至L3缓存,在多核计算机中会有多个CPU,那么就会存在多套缓存,在这多套缓存之间的数据就可能出现不一致的现象。为了解决这个问题,有了内存模型。内存模型定义了共享内存系统中多线程程序读写操作行为的规范。通过这些规则来规范对内存的读写操作,从而保证指令执行的正确性。

其实Java内存模型告诉我们通过使用关键词 “synchronized”“volatile” 可以让Java保证某些约束:

1.“volatile” — 保证读写的都是主内存的变量。
2.“synchronized” — 保证在块开始时都同步主内存的值到工作内存,而块结束时将变量同步回主内存。

通过以上描述我们就可以写出线程安全的Java程序,JDK也同时帮我们屏蔽了很多底层的东西。

所以,在编译器各种优化及多种类型的微架构平台上,Java语言规范制定者试图创建一个虚拟的概念并传递到Java程序员,让他们能够在这个虚拟的概念上写出线程安全的程序来,而编译器实现者会根据Java语言规范中的各种约束在不同的平台上达到Java程序员所需要的线程安全这个目的

那么,在多种类型微架构平台上,又是如何解决缓存不一致性问题的呢?这是众多CPU厂商必须解决的问题。为了解决前面提到的缓存数据不一致的问题,人们提出过很多方案,通常来说有以下2种方案:

1.通过在总线加LOCK#锁的方式;
2.通过缓存一致性协议(Cache Coherence Protocol);

2.1 总线的概念

首先,上面的两种方案,其实都涉及到了总线的概念,那到底什么是总线呢?总线是处理器与主存以及处理器与处理器之间进行通信的媒介,有两种基本的互联结构:SMP(symmetric multiprocessing 对称多处理)和NUMA(nonuniform memory access 非一致内存访问)

SMP系统结构非常普通,因为它们最容易构建,很多小型服务器采用这种结构。处理器和存储器之间采用总线互联,处理器和存储器都有负责发送和监听总线广播的信息的总线控制单元。但是同一时刻只能有一个处理器(或存储控制器)在总线上广播,所有的处理器都可以监听。很容易看出,对总线的使用是SMP结构的瓶颈。

NUMP系统结构中,一系列节点通过点对点网络互联,像一个小型互联网,每个节点包含一个或多个处理器和一个本地存储器。一个节点的本地存储对于其他节点是可见的,所有节点的本地存储一起形成了一个可以被所有处理器共享的全局存储器。可以看出,NUMP的本地存储是共享的,而不是私有的,这点和SMP是不同的。NUMP的问题是网络比总线复制,需要更加复杂的协议,处理器访问自己节点的存储器速度快于访问其他节点的存储器。NUMP的扩展性很好,所以目前很多大中型的服务器在采用NUMP结构

对于上层程序员来说,最需要理解的是总线线是一种重要的资源,使用的好坏会直接影响程序的执行性能

2.2 总线加Lock

在早期的CPU当中,是可以通过在总线上加LOCK#锁的形式来解决缓存不一致的问题。因为CPU和其他部件进行通信都是通过总线来进行的,如果对总线加LOCK#锁的话,也就是说阻塞了其他CPU对其他部件访问(如内存),从而使得只能有一个CPU能使用这个变量的内存。在总线上发出了LCOK#锁的信号,那么只有等待这段代码完全执行完毕之后,其他CPU才能从其内存读取变量,然后进行相应的操作。这样就解决了缓存不一致的问题。

但是由于在锁住总线期间,其他CPU无法访问内存,会导致效率低下。因此出现了第二种解决方案,通过缓存一致性协议来解决缓存一致性问题。

2.3 缓存一致性协议

一致性要求是指,若cache中某个字段被修改,那么在主存(以及更高层次)上,该字段的副本必须立即或最后加以修改,并确保它者引用主存上该字内容的正确性。    当代多处理器系统中,每个处理器大都有自己的cache。同一主存块的拷贝能同时存于不同cache中,若允许处理器各自独立地修改自己的cache,就会出现不一致问题。解决此问题有软件办法和硬件办法。硬件办法能动态地识别出不一致产生的条件并予以及时处理,从而使cache的使用有很高的效率。并且此办法对程序员和系统软件开发人员是透明的,减轻了软件研制负担,从而普遍被采用。

软件办法最出名的就是Intel 的MESI协议,MESI协议保证了每个缓存中使用的共享变量的副本是一致的。MESI协议是一种采用写–无效方式的监听协议。它要求每个cache行有两个状态位,用于描述该行当前是处于修改态(M)、专有态(E)、共享态(S)或者无效态(I)中的哪种状态,从而决定它的读/写操作行为。这四种状态的定义是:

1.修改态(Modified):这行数据有效,数据被修改了,和内存中的数据不一致,数据只存在于本Cache中。
2.专有态(Exclusive):这行数据有效,数据和内存中的数据一致,数据只存在于本Cache中。
3.共享态(Shared):这行数据有效,数据和内存中的数据一致,数据存在于很多Cache中。
4.无效态(Invalid):这行数据无效。

MESI协议适合以总线为互连机构的多处理器系统各cache控制器除负责响应自己CPU的内存读写操作(包括读/写命中与未命中)外,还要负责监听总线上的其它CPU的内存读写活动(包括读监听命中与写监听命中)并对自己的cache予以相应处理所有这些处理过程要维护cache一致性,必须符合MESI协议状态转换规则

下面由图的四个顶点出发,介绍总线监听与状态转换规则:

1.该I无效行在 自身Cache读未命中 将被相应内存块填充以建立新行时,读监听命中,说明其它Cache正在读同地址的内存块,以建立新行。故为多Cache共享行,应为S状态,并应继续发出读监听广播,使其它Cache的类似情况效仿。
2.该I无效行在 自身Cache读未命中 将被相应内存块填充以建立新行时,未有读监听命中,为本Cache专有,故新建行应为E状态。
3.该I无效行在 自身Cache写未命中 时,将先读入相应内存块填充新行后,再进行写修改,与原内存正本的数据不一致,故新建行为M状态。
4.该S共享行 写监听命中,说明别的Cache由于写命中修改了同此地址的行,根据写无效原则,此共享行应改变为无效(I)状态。
5.该S共享行 读命中,状态不变。
6.该S共享行 读监听命中,说明其它Cache正在读同地址内存块,以建立新行,此时该共享行状态不必改变,但应继续发读监听广播,供它者监听。
7.该S共享行 写命中,其中某字被改写,与内存正本不一致,故应改为M状态,且应发出共享行写命中监听广播,使其它Cache同地址行作废(同 4)。
8.该E专有行 读监听命中,说明别的Cache正在读同地址的内存正本,以建立新行,故其状态应改为S状态,并发出读监听广播,以使同此情况及1效仿之。
9.该E专有行 读命中 不必改变状态。
10.该E专有行 写监听命中,说明别的Cache由于写未命中而访问同地址的内存正本,该E态行内容即将过时,故应作废。
11.该E专有行 写命中,只改变状态为M态即可,无须他者监听。
12.该M修改行 写命中 状态不变。
13.该M修改行 读命中 状态不变。
14.该M修改行 读监听命中,应将该行最新数据写回内存正本后变为S状态。并发出读监听广播,供他者监听。
15.该M修改行 写监听命中,说明别的Cache由于写未命中而访问了同地址的内存块(同3),将实行先读后修改,此时本地M修改行应抢先写回主存,然后作废,以保证别的Cache读出整行而未被修改数据的正确性
16.该M修改行 整行写监听命中,说明别的Cache由于写未命中而访问了同地址的内存块,将实行先读后整行的修改,此时本地M修改行不必写回主存,只作废即可。

上述分析可以看出,虽然各cache控制器随时都在监听系统总线,但能监听到的只有读未命中、写未命中以及共享行写命中三种情况。总线监控逻辑并不复杂,增添的系统总线传输开销也不大,MESI协议却有力地保证了主存块脏拷贝在多cache中的唯一性,并能及时写回,保证cache主存存取的正确性。

但是,值得注意的是,传统的MESI协议中有两个行为的执行成本比较大。一个是将某个Cache Line标记为Invalid状态,另一个是当某Cache Line当前状态为Invalid时写入新的数据。所以CPU通过Store Buffer和Invalidate Queue组件来降低这类操作的延时。如下图所示:

1.当一个CPU进行写入时,首先会给其它CPU发送Invalid消息,然后把当前写入的数据写入到Store Buffer中,然后异步在某个时刻真正的写入到Cache中。
2.当前CPU核如果要读Cache中的数据,需要先扫描Store Buffer之后再读取Cache
3.但是此时其它CPU核是看不到当前核的Store Buffer中的数据的,要等到Store Buffer中的数据被刷到了Cache之后才会触发失效操作
4.而当一个CPU核收到Invalid消息时,会把消息写入自身的Invalidate Queue中,随后异步将其设为Invalid状态
5.和Store Buffer不同的是,当前CPU核心使用Cache时并不扫描Invalidate Queue部分,所以可能会有极短时间的脏读问题

所以,MESI协议,可以保证缓存的一致性,但是无法保证实时性,可能会有极短时间的脏读问题

把消息写入自身的Invalidate Queue中,随后异步将其设为Invalid状态**。

5.和Store Buffer不同的是,当前CPU核心使用Cache时并不扫描Invalidate Queue部分,所以可能会有极短时间的脏读问题

所以,MESI协议,可以保证缓存的一致性,但是无法保证实时性,可能会有极短时间的脏读问题

其实,并非所有情况都会使用缓存一致性的,如:被操作的数据不能被缓存在CPU内部或操作数据跨越多个缓存行(状态无法标识),则处理器会调用总线锁定;另外当CPU不支持缓存锁定时,自然也只能用总线锁定了,比如说奔腾486以及更老的CPU。## 题外话

初入计算机行业的人或者大学计算机相关专业毕业生,很多因缺少实战经验,就业处处碰壁。下面我们来看两组数据:

2023届全国高校毕业生预计达到1158万人,就业形势严峻;

国家网络安全宣传周公布的数据显示,到2027年我国网络安全人员缺口将达327万。

一方面是每年应届毕业生就业形势严峻,一方面是网络安全人才百万缺口。

6月9日,麦可思研究2023年版就业蓝皮书(包括《2023年中国本科生就业报告》《2023年中国高职生就业报告》)正式发布。

2022届大学毕业生月收入较高的前10个专业

本科计算机类、高职自动化类专业月收入较高。2022届本科计算机类、高职自动化类专业月收入分别为6863元、5339元。其中,本科计算机类专业起薪与2021届基本持平,高职自动化类月收入增长明显,2022届反超铁道运输类专业(5295元)排在第一位。

具体看专业,2022届本科月收入较高的专业是信息安全(7579元)。对比2018届,电子科学与技术、自动化等与人工智能相关的本科专业表现不俗,较五年前起薪涨幅均达到了19%。数据科学与大数据技术虽是近年新增专业但表现亮眼,已跻身2022届本科毕业生毕业半年后月收入较高专业前三。五年前唯一进入本科高薪榜前10的人文社科类专业——法语已退出前10之列。
外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

“没有网络安全就没有国家安全”。当前,网络安全已被提升到国家战略的高度,成为影响国家安全、社会稳定至关重要的因素之一。

网络安全行业特点

1、就业薪资非常高,涨薪快 2022年猎聘网发布网络安全行业就业薪资行业最高人均33.77万!

img

2、人才缺口大,就业机会多

2019年9月18日《中华人民共和国中央人民政府》官方网站发表:我国网络空间安全人才 需求140万人,而全国各大学校每年培养的人员不到1.5W人。猎聘网《2021年上半年网络安全报告》预测2027年网安人才需求300W,现在从事网络安全行业的从业人员只有10W人。
img

行业发展空间大,岗位非常多

网络安全行业产业以来,随即新增加了几十个网络安全行业岗位︰网络安全专家、网络安全分析师、安全咨询师、网络安全工程师、安全架构师、安全运维工程师、渗透工程师、信息安全管理员、数据安全工程师、网络安全运营工程师、网络安全应急响应工程师、数据鉴定师、网络安全产品经理、网络安全服务工程师、网络安全培训师、网络安全审计员、威胁情报分析工程师、灾难恢复专业人员、实战攻防专业人员…

职业增值潜力大

网络安全专业具有很强的技术特性,尤其是掌握工作中的核心网络架构、安全技术,在职业发展上具有不可替代的竞争优势。

随着个人能力的不断提升,所从事工作的职业价值也会随着自身经验的丰富以及项目运作的成熟,升值空间一路看涨,这也是为什么受大家欢迎的主要原因。

从某种程度来讲,在网络安全领域,跟医生职业一样,越老越吃香,因为技术愈加成熟,自然工作会受到重视,升职加薪则是水到渠成之事。

黑客&网络安全如何学习

今天只要你给我的文章点赞,我私藏的网安学习资料一样免费共享给你们,来看看有哪些东西。

1.学习路线图

行业发展空间大,岗位非常多

网络安全行业产业以来,随即新增加了几十个网络安全行业岗位︰网络安全专家、网络安全分析师、安全咨询师、网络安全工程师、安全架构师、安全运维工程师、渗透工程师、信息安全管理员、数据安全工程师、网络安全运营工程师、网络安全应急响应工程师、数据鉴定师、网络安全产品经理、网络安全服务工程师、网络安全培训师、网络安全审计员、威胁情报分析工程师、灾难恢复专业人员、实战攻防专业人员…

职业增值潜力大

网络安全专业具有很强的技术特性,尤其是掌握工作中的核心网络架构、安全技术,在职业发展上具有不可替代的竞争优势。

随着个人能力的不断提升,所从事工作的职业价值也会随着自身经验的丰富以及项目运作的成熟,升值空间一路看涨,这也是为什么受大家欢迎的主要原因。

从某种程度来讲,在网络安全领域,跟医生职业一样,越老越吃香,因为技术愈加成熟,自然工作会受到重视,升职加薪则是水到渠成之事。

黑客&网络安全如何学习

今天只要你给我的文章点赞,我私藏的网安学习资料一样免费共享给你们,来看看有哪些东西。

1.学习路线图

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

攻击和防守要学的东西也不少,具体要学的东西我都写在了上面的路线图,如果你能学完它们,你去就业和接私活完全没有问题。

2.视频教程

网上虽然也有很多的学习资源,但基本上都残缺不全的,这是我自己录的网安视频教程,上面路线图的每一个知识点,我都有配套的视频讲解。

内容涵盖了网络安全法学习、网络安全运营等保测评、渗透测试基础、漏洞详解、计算机基础知识等,都是网络安全入门必知必会的学习内容。
外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

(都打包成一块的了,不能一一展开,总共300多集)

因篇幅有限,仅展示部分资料,需要点击下方链接即可前往获取

如果你对网络安全入门感兴趣,那么你需要的话可以点击这里👉网络安全重磅福利:入门&进阶全套282G学习资源包免费分享!

3.技术文档和电子书

技术文档也是我自己整理的,包括我参加大型网安行动、CTF和挖SRC漏洞的经验和技术要点,电子书也有200多本,由于内容的敏感性,我就不一一展示了。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

因篇幅有限,仅展示部分资料,需要点击下方链接即可前往获取

如果你对网络安全入门感兴趣,那么你需要的话可以点击这里👉网络安全重磅福利:入门&进阶全套282G学习资源包免费分享!

4.工具包、面试题和源码

“工欲善其事必先利其器”我为大家总结出了最受欢迎的几十款款黑客工具。涉及范围主要集中在 信息收集、Android黑客工具、自动化工具、网络钓鱼等,感兴趣的同学不容错过。

还有我视频里讲的案例源码和对应的工具包,需要的话也可以拿走。

因篇幅有限,仅展示部分资料,需要点击下方链接即可前往获取
如果你对网络安全入门感兴趣,那么你需要的话可以点击这里👉网络安全重磅福利:入门&进阶全套282G学习资源包免费分享!

最后就是我这几年整理的网安方面的面试题,如果你是要找网安方面的工作,它们绝对能帮你大忙。

这些题目都是大家在面试深信服、奇安信、腾讯或者其它大厂面试时经常遇到的,如果大家有好的题目或者好的见解欢迎分享。

参考解析:深信服官网、奇安信官网、Freebuf、csdn等

内容特点:条理清晰,含图像化表示更加易懂。

内容概要:包括 内网、操作系统、协议、渗透测试、安服、漏洞、注入、XSS、CSRF、SSRF、文件上传、文件下载、文件包含、XXE、逻辑漏洞、工具、SQLmap、NMAP、BP、MSF…

img

因篇幅有限,仅展示部分资料,需要点击下方链接即可前往获取

如果你对网络安全入门感兴趣,那么你需要的话可以点击这里👉网络安全重磅福利:入门&进阶全套282G学习资源包免费分享!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值