CPU多级缓存
-
主内存(RAM):也就是所谓的内存条,一般当数据写入电脑磁盘的时候需要先写入主内存,然后再写入磁盘。
-
系统主线:连接系统cpu和主内存的系统主线,就比如台式电脑连接主板上连接cpu和内存条的主线。
-
如下图所示最简单的高速缓存的配置,数据的读取和存储都经过高速缓存,CPU核心与高速缓存有一条特殊的快速通道;主存和高速缓存都连在系统总线上,这条总线还用于其他组件的通信。
-
高速缓存出现不久,系统变得越来越复杂,高速缓存与主存之间的速度差异被拉大,直到加入了另一级缓存,新加入的这级缓存比第一缓存更大,而且更慢,而且经济上不合适,所以有了二级缓存,甚至是三级缓存。
一、为什么需要CPU多级缓存 -
在计算机最开始的时候,cpu直接于主内存交换数据,不知道读者对摩尔定律有没有印象:当价格不变时,集成电路上可容纳的元器件的数目,约每隔18-24个月便会增加一倍,性能也将提升一倍,由于cpu性能日新月异的增长,远高于主内存读写,而需要等待主内存,这样发挥不了多核cpu高性能的优势,于是便增加了cpu多级缓存,在现在电脑的任务管理器也是看的到的:
二、CPU缓存的意义:
- 时间局部性:如果某个数据被访问,那么在不久的将来它很有可能会被再次访问。
- 空间局部性:如果某个数据被访问,那么与它相邻的数据很快也能被访问。
三、java线程内存模型
- java线程内存模型和CPU缓存模型类似,是基于CPU缓存模型来建立的,
java线程内存模型是标准化的,屏蔽掉了底层不同计算机的区别。
举个例子:
输出日志:
说明了两个线程间读取共享变量存在问题,第二个线程执行完成后initFlag已经置为true;而第一个线程无法获知initFlag已经改变。
在共享变量中加上volatile关键字,保持多线程之间共享变量在多个线程之间工作内存的可见性,则线程1中的initFlag变量同步。
那么volatile 是怎么实现共享变量在多个线程之间工作内存的可见性的呢?
首先来看一下JMM的8种数据原子操作:
- read 读取,作用于主内存把变量从主内存中读取到本本地内存。
- load 加载,把从主内存中读取的变量加载到本地内存的变量副本中
- use 使用,把工作内存中的一个变量值传递给执行引擎,每当虚拟机遇到一个
需要使用变量的值的字节码指令时将会执行这个操作。、 - assign 赋值 它把一个从执行引擎接收到的值赋值给工作内存的变量,
每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作。 - store 存储 ,把工作内存中的一个变量的值传送到主内存中,以便随后的write的操作。
- write 写入 ,它把store操作从工作内存中一个变量的值传送到主内存的变量中。
- lock 锁定 :作用于主内存的变量,把一个变量标识为一条线程独占状态。
- unlock 解锁:作用于主内存变量,把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定。
- 线程1:先把initFlag变量read读取出来,再load载入工作内存,use使用线程1执行代码!initFlag
- 线程2:先把initFlag变量read读取出来,再load载入工作内存,use使用线程2执行代码initFlag=true,
再assign重新赋值,store存储并写入主内存,write写入到主内存中的变量。(线程2对缓存行lock加锁,
write写入主内存后会解锁unlock,防止initFlag还未write写入主内存就被线程1读取为false)。 - 线程1:因为initFlag被volatile修饰,使用MESI缓存一致性协议,线程1cpu总线嗅探机制监听到了
initFlag值的修改,线程1中initFlag=false失效变为true退出循环继续执行,
体现了多线程同步运行共享变量副本的可见性。如果initFlag没有被volatile修饰,
线程1将感知不到initFlag的变化,一直循环下去停止不了。
图中的代码:
造成jmm缓存不一致的问题解决办法
-
总线加锁(性能太低):
cpu从主内存读取数据到高速缓存,会在总线对这个数据加锁,这样其它cpu没法去读或写这个数据,
直到这个cpu使用完数据释放锁之后其它cpu才能读取该数据,导致结果:改并行为串行。
-
MESI缓存一致性协议
多个cpu从主内存读取同一个数据到各自的高速缓存,当其中某个cpu修改了缓存里的数据,
该数据会马上同步回主内存,其它cpu通过总线嗅探机制可以感知到数据的变化从而将
自己缓存里的数据失效。