一、缓存在计算机中的结构和大小
在正式开始之前,我们得无比先了解缓存在计算机中的结构和大小如何:
首先查看我们自己电脑的缓存; windows 下,Ctrl + alt + .(点), 打开任务管理器,性能栏底部:
至于缓存为什么能提高计算机的速度,这个就得打个比方了:我们以在图书馆借书为例,如:《三体》这本小说比较受欢迎,大家都想要借这本书看,每天都会有同学来询问,别人是否已经将书还回来了,他也想要借阅,那么上一位同学看完之后还给图书馆,图书馆需要将它立即整理放入书架吗?答案是不需要的,直接放在借阅处,因为借阅的人多,放回书架后,下一个人借阅时还得在书籍中进行查找,这就有点儿浪费时间了
二、CAS 机制
CAS时CompareAndSet 的缩写,意思时比较设置,CAS它时一种乐观锁,它认为短时间内它能访问到共享资源并对其修改成功,所以,经常看到一个死循环包裹这它。由于缓存的存在,会出现这样一种状态:两个线程同时对一个变量进行修改,则后面写入主存的数据会将前面写入主存的数据覆盖掉,如(由于缓存的存在,这完全是可能的):
CAS做的就是将值进行修改,写入主存前先进行判断,值是否还是以前那个值,如:线程二将5改为7后,写入主存前先检查 x 是否还等于5,等于5则进行修改,不等于5则证明已经被修改过了,此时便不能进行修改,要进行修改的话,需要重新从主存中获取x 的值,再进行修改。然而,这样还是存在问题的,因为仍然存在一种情况,即线程二将5改为7后,写入主存之前,线程一先将5改为6,然后又将6改为5,此时这种情况仍然是存在问题的,因为此时的5已非彼时的5了。它的解决方法是再加上一个版本号,这样用以区分识别。
Ⅲ、缓存行
缓存的最小组成单位是缓存行,每一个缓存都是由很多个缓存行组成的。没个缓存行的大小一般都是32字节或64字节。缓存中的数据操作也是以缓存行为单位的,也就是一个缓存行上的数据会被同时被操作,一个java long类型占8个字节当有一个 long 被加载到缓存中,将会无消耗的加载另外的 (32-8=24)/(64-8=56) 个字节里面的内容
Ⅳ、MESI 协议
多核的 CPU 都有自己的缓存(一般为一二级缓存),以及一个 CPU插槽之间的核共享缓存(一般为三级缓存),不同核心的 CPU 缓存中难免会加载同样的数据,那么如何保证缓存的一致性呢,就得靠 MESI协议了,在 MESI 协议中,每一个缓存行都有4个状态,可用2个bit表示,分别是:
- M(Modified):这行数据有效,数据被修改了,和内存中的数据不一致,数据只存在于本缓存中
- E(Exclusive): 这行数据有效,数据和内存中的数据不一致,数据值存在于本缓存中
- S(Shared): 这行数据有效,数据和内存中的数据一致,数据存在于很多缓存中
- I(Invalid):这行数据无效
Ⅴ、缓存伪共享
在上面已经说到缓存时以缓存行的形式加载的,也就是会出现如上图的情况。这样便会存在一个问题,在CPU1这边更改 a 的值,b的值不改变;则会刷新所有和 a 相关的缓存数据,b的变量就会收到牵连,导致 CPU2再去缓存读取 b 变量时出现 miss,要重新到主存中加载新的数据,这就是伪共享
它的解决方案:填充数据,以空间换时间:
Java8中的使用:
① 添加注解 @sun.misc.Contended
② 对jvm进行设置: -XX:-RestrictContended
参考:https://blog.csdn.net/weixin_40288381/article/details/89311425
https://blog.csdn.net/hanmindaxiongdi/article/details/81159314
https://www.jianshu.com/p/7f89650367b8
http://ifeve.com/%E4%BC%AA%E5%85%B1%E4%BA%AB/