hello,你好鸭,我是Ethan,西安电子科技大学大三在读,很高兴你能来阅读。
✔️目前博客主要更新Java系列、项目案例、计算机必学四件套等。
人生之义,在于追求,不在成败,勤通大道。加油呀!
个人主页:Ethan Yankang
推荐:史上最强八股文||一分钟看完我的几百篇博客
温馨提示:划到文末发现专栏彩蛋 点击这里直接传送
本篇概览:详细讲解了缓存行的一致性协议之一的MEESI的方方面面。⭕
【计算机领域一切迷惑的源头都是基本概念的模糊,算法除外】
JAVA八股文就是最最基础之事,此关不过,啥都没有。今日得《黑马程序员之八股》良品辅助,代码随想录之优品引导,《JAVA核心技术》之极品传教,应按此三者学之习之,时时复习,长此以往必能穿魂入脉,习得大功。
应该四处查阅浏览八股技术博客之,不应固守一隅,应集百家之所长而习得内功。
对于一个强烈想完全掌握JAVA的技术宅来说,JAVA的XXX万万不能放过,这些基础的概念例程都值得细细体味的,千万别觉得都是文字,浪费时间,记住——别违背科学发展的客观规律。别一味地赶进度以满足自己学的都么快的虚荣心,自欺欺人,要老老实实的走好每一步。
每一次复习八股文都是复习JAVA基础的绝佳机会,切借此机会融会贯通之。
史上最全JAVA八股文,欢迎收藏 -CSDN博客
所有JAVA基础一键查阅(含习题集)-CSDN博客
public class CacheLinePanding_01 {
public static volatile long[] arr =new long[2];
public static void main(String[] args) throws InterruptedException {
Thread thread_1 = new Thread(() -> {
for (long i = 0; i < 1_0_0000_0000L; i++) {
arr[0] = i;
}
});
Thread thread_2 = new Thread(() -> {
for (long i = 0; i < 1_0_0000_0000L; i++) {
arr[1] = i;
}
});
final long start_time=System.nanoTime();
thread_1.start();
thread_2.start();
thread_1.join();
thread_2.join();
System.out.println((System.nanoTime()-start_time)/100_0000);
}
}
public class CacheLinePanding_02 {
public static volatile long[] arr =new long[16];
public static void main(String[] args) throws InterruptedException {
Thread thread_1 = new Thread(() -> {
for (long i = 0; i < 1_0_0000_0000L; i++) {
arr[0] = i;
}
});
//注意这里与上面的程序的区别在于这里的arr[0]与arr[8]一定不在同一个缓存行。
// 0-8之间就隔了64个字节,两者在两端。
Thread thread_2 = new Thread(() -> {
for (long i = 0; i < 1_0_0000_0000L; i++) {
arr[8] = i;
}
});
final long start_time=System.nanoTime();
thread_1.start();
thread_2.start();
// 调用 thread_1.join() 和 thread_2.join() ,使得 main 线程会暂停执行,
// 等待 thread_1 和 thread_2 线程结束后,main 线程才会继续执行后续的代码。
thread_1.join();
thread_2.join();
System.out.println((System.nanoTime()-start_time)/100_0000);
}
}
上述两个实验的源码基本一致,功能是主线程里面 分别开启两个线程,在这两个线程里面对数组进行赋值,最后程序输出两个线程 的整个执行时间。
这里利用了System.nanoTime()来精确计时。
按理说只有线程里面的数组赋值的对象不同。就第二个实验的第二个线程是操作arr[8],但是试验结果却千差万别:如下:
为什么? 第一个实验的时间>>第二个实验的时间
原因就是第一个实验中的两个线程的两个赋值数组元素一定是在同一缓存行,这样两个线程的进入不同CPU中核心执行后,一旦x(arr[0])改变了,就必须通知另一个线程中的同一缓存行,下次再次赋值时就会从主存中重新读取最新的x。(注意这里不是volatile关键字的作用,其实去掉关键字在测试也一样)主要这是CPU级别的特性——MESI——缓存行数据一致性中的一种。
(核心就是一但同一个缓存行中的数据在某线程中发生更改,就必须通知读取该缓存行中的其它线程,我改变了。)
比如实验一这里就是两个核心同时拿到同一个缓存行,需要同步,这就是实验一耗时长的原因
MESI本质就是每一颗缓存行的不同状态。
M :modified 直接从本地内存中读取
E :exclusive 直接从本地内存中读取
S :shared 从其他CPU的缓存中读取
I :invalid 需要从主内存中读取
MESI 协议的基本思想是通过在缓存行上添加状态位和监听机制,来实现缓存一致性。当一个处理器读取一个缓存行时,如果该缓存行处于Modified 或 Exclusive 状态,则直接从本地缓存中读取;如果处于 Shared 状态,则需要从其他拥有该缓存行的处理器中获取最新数据;如果处于 Invalid 状态,则需要从主内存中读取。
当一个处理器修改一个缓存行时,它会将该缓存行标记为 Modified 状态,并通知其他拥有该缓存行的处理器将其标记为 Invalid 状态。其他处理器在接收到通知后,会将其缓存中的该缓存行标记为 Invalid 状态,并在下次访问时从主内存中重新读取。
Modified 表示 CPU 拥有该 Cache Line 且将其做了修改,需要在重用该 Cache Line 存其它数据前,将修改的数据写入主存,或者将 Cache Line 转交给其它 CPU 所有;Exclusive 跟 Modified 类似,也表示 CPU 拥有某个 Cache Line 但还未来得及对它做出修改,CPU 可以直接将里面数据丢弃或者转交给其它 CPU。
市面上的缓存一致性协议很多,MESI只是一种,两者是包含关系。MESI是最主流的,Intel的。
单机版最快的MQ,本质是环形缓存,上面的数据存放是有一个环形指针在环上一直走,为了避免这个指针与其他的东西在一个缓存行,从而带来效率上的降低(MESI的结果),部分源码如下:
指针前面有7的8字节的数据,共56字节,加上自身指针就有64字节了,就是一个缓存行的大小。后面也是这样,占一个缓存行大小。所以无论前后,这样这个指针就永远独占一个缓存行了。没有其他的数据更改能影响它。效率嘎嘎高。
热门专栏推荐
计算机科学入门系列 关注走一波
CSAPP深入理解计算机原理 关注走一波
微服务项目之黑马头条 关注走一波
redis深度项目之黑马点评 关注走一波
JAVA面试八股文系列专栏 关注走一波
JAVA基础试题集精讲 关注走一波
代码随想录精讲200题 关注走一波
总栏
JAVA基础要夯牢 关注走一波
JAVA后端技术栈 关注走一波
JAVA面试八股文 关注走一波
JAVA项目(含源码深度剖析) 关注走一波
计算机四件套 关注走一波
数据结构与算法 关注走一波
必知必会工具集 关注走一波
书籍网课笔记汇总 关注走一波
非常感谢你阅读到这里,如果这篇文章对你有帮助,希望能留下你的点赞 关注❤收藏✅ 评论,大佬三连必回哦!thanks!!!
愿大家都能学有所得,功不唐捐!