CPU Cache模型与JMM

1、CPU Cache模型

1.1 产生原因

在计算机中,所有的运算操作都是由CPU的寄存器完成的,CPU指令的执行过程需要涉及数据的读取和写入操作,CPU访问的所有数据都来自主存。随着技术进步,CPU的处理速度与内存的访问速度之间的差距越来越大,此时CPU直连内存的访问方式会限制CPU,降低CPU整体的吞吐量,于是就产生了缓存的设计。


1.2 模型结构

clip_image001_thumb

CPU Cache的读写速度远高于内存的速度,缓解了CPU直接访问内存效率低下的问题,极大提高了CPU吞吐能力。在程序运行过程中,会将运算所需要的数据从主存复制一份到CPU Cache中,CPU进行计算时直接对Cache中的数据进行读写,当运算结束后,再将Cache中的最新数据刷新到主内存中。

clip_image002_thumb

1.3 CPU缓存一致性问题

  1 public class IncreaseTest {
  2     private static int num = 0;
  3 
  4     public synchronized static void increase() {
  5         num++;
  6     }
  7 
  8     public static int get() {
  9         return num;
 10     }
 11 
 12     public static void main(String[] args) throws InterruptedException {
 13         new Thread(() -> {
 14             for (int i = 0; i < 100000; i++) {
 15                 increase();
 16             }
 17         }).start();
 18     }
 19 }
hashCode

执行以上代码,单线程下结果为10000,但多线程下就会得到小于等于10000的结果。具体原因,在执行num++操作时,会有以下具体过程:

(1) 读取主内存的num到CPU Cache中

(2) 对num进行加一操作

(3) 将结果写回到CPU Cache中

(4) 将数据刷新到主内存中

多线程向下,每个线程都有自己的工作内存(本地内存,对应于CPU中的Cache),则变量num会在多个线程下都存在一个副本。假设现有两个线程在执行num++,都从主存中取得num=0存入CPU Cache中,线程一计算后num=1写入主存,线程二计算后也写入主存,则经过两次自增后结果仍为1,这就是缓存不一致问题。

clip_image004_thumb

1.4 解决缓存不一致问题

1.4.1 总线加锁方式

clip_image005_thumb

CPU和其他组件通信是通过各种总线进行的,采用总线加锁,每次只有一个CPU访问这个变量的内存,从并行退化为串行执行。即每次只有一个线程获取到num(read主存),并进行自增操作,写回主存后(write主存),完成这一过程后,下一个线程才能获取num执行操作。这种方式效率低下。


1.4.2 缓存一致性协议

其大致思想是,当CPU在操作Cache中的数据时,如果发现该变量是一个共享变量,也就是在其他CPU Cache中也存在一个副本,那么执行以下操作:

(1) 读取操作,不做任何处理,只是将Cache中的数据读取到寄存器

(2) 写入操作,发出信号通知其他CPU将该变量的Cache line置为无效状态,写入成功后,此时其他CPU通过总线嗅探机制得知写入成功,就再次从主存中获取该共享变量。

缓存一致性协议内容很复杂,以上只是大致过程。


2、Java内存模型

Java Memory Mode指定了Jav虚拟机如何与计算机的主存进行工作。Java内存模型决定了一个线程对共享变量的写入何时对其他线程可见,Java内存模型定义了线程和主存之间的抽象关系,具体如下:

(1) 共享变量存储于主存之中,每个线程都可以访问

(2) 每个线程都有私有的工作内存或者称为本地内存

(3) 工作内存只存储该线程对共享变量的副本

(4) 线程不能直接操作主内存,只有先操作了工作内存之后才能写入主内存

(5) 工作内存和Java内存模型一样也是一个抽象概念,其实并不存在,它涵盖了缓存、寄存器、编译器优化以及硬件等

clip_image006_thumb[1]

猜你喜欢

转载自www.cnblogs.com/privateNotesOfAidanChen/p/12897881.html