爱生活,爱编码,本文已收录架构技术专栏关注这个喜欢分享的地方。本文 架构技术专栏 已收录,有各种JVM、多线程、源码视频、资料以及技术文章等你来拿
零、开局前两天我搞了两个每日一个知识点,对多线程并发的部分知识做了下概括性的总结。但通过小伙伴的反馈是,那玩意写的比较抽象,看的云里雾里晕晕乎乎的。
所以又针对多线程底层这一块再重新做下系统性的讲解。
有兴趣的朋友可以先看下前两节,可以说是个笼统的概念版。
好了,回归正题。在多线程并发的世界里synchronized、volatile、JMM是我们绕不过去的技术坎,而重排序、可见性、内存屏障又有时候搞得你一脸懵逼。有道是知其然知其所以然,了解了底层的原理性问题,不论是日常写BUG还是面试都是必备神器了。
先看几个问题点:
1、处理器与内存之间是怎么交互的?
2、什么是缓存一致性协议?
3、高速缓存内的消息是怎么更新变化的?
4、内存屏障又和他们有什么关系?
如果上面的问题你都能倒背如流,那就去看看电影放松下吧!
一、高速缓存目前的处理器的处理能力要远远的胜于主内存(DRAM)访问的效率,往往主内存执行一次读写操作所需的时间足够处理器执行上百次指令。所以为了填补处理器与主内存之间的差距,设计者们在主内存和处理器直接引入了高速缓存(Cache)。如图:
其实在现代处理器中,会有多级高速缓存。一般我们会成为一级缓存(L1 Cache)、二级缓存(L2 Cache)、三级缓存(L3 Cache)等,其中一级缓存一般会被集成在CPU内核中。如图:
内部结构高速缓存存在于每个处理器内,处理器在执行读、写操作的时候并不需要直接与内存交互,而是通过高速缓存进行。
高速缓存内其实就是为应用程序访问的变量保存了一个数据副本。高速缓存相当于一个容量极小的散列表(Hash Table),其键是一个内存地址,值是内存数据的副本或是我们准备写入的数据。从其内部来看,其实相当于一个拉链散列表,也就是包含了很多桶,每个桶上又可以包含很多缓存条目(想想HashMap),如图:
缓存条目在每个缓存条目中,其实又包含了Tag、Data Block、Flag三个部分,咱们来个小图:
**Data Block : **也就是我们常常叨叨的缓存行(Cache Line),她其实是高速缓存与主内存间进行数据交互的最小单元,里面存储着我们需要的变量数据。
**Tag : **包含了缓存行中数据内存地址的信息(其实是内存地址的高位部分的比特)
Flag : 标识了当前缓存行的状态(MESI咯)
那么,我们的处理器又是怎么寻找到我们需要的变量呢?
不多说,上图:
其实,在处理器执行内存访问变量的操作时,会对内存地址进行解码的(由高速缓存控制器执行)。而解码后就会得到tag、index 、offset三部分数据。
index : 我们知道高速缓存内的结构是一个拉链散列表,所以index就是为了帮我们来定位到底是哪个缓存条目的。
tag : 很明显和我们缓存条目中的Tag 一样,所以tag 相当于缓存条目的编号。主要用于,在同一个桶下的拉链中来寻找我们的目标。
offset : 我们要知道一个前提,就是一个缓存条目中的缓存行是可以存储很多变量的,所以offset的作用是用来确定一个变量在缓存行中的起始位置。
所以,在如果在高速缓存内能找到缓存条目并且定位到了响应得缓存行,而此时缓存条目的Flag标识为有效状态,这时候也就是我们所说的缓存命中(Cache Hit),否则就是缓存未命中(Cache Miss)。
缓存未命有包括读未命中(Read Miss)和写未命中(Write Miss)两种,对应着对内存的读写操作。
而在读未命中(Read Miss) 产生时,处理器所需要的数据会从主内存加载并被存入高速缓存对应的缓存行中,此过程会导致处理器停顿(Stall)而不能执行其他指令。
二、缓存一致性协议