Java并发编程(一):计算机内存模型相关概念

本文深入探讨了CPU高速缓存的工作原理及其在多线程环境下的缓存一致性问题,介绍了总线锁机制与缓存一致性协议两种解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、CPU高速缓存

1、为什么需要高速缓存

计算机在执行程序时,程序的每条指令都是在CPU中执行的。而执行指令的同时势必会伴随着数据的读取和写入。由于程序运行过程的临时数据都是存储在内存中的(主内存-物理内存),而内存的读写相对于CPU的执行速度来说是非常慢的。

如果CPU执行指令所需要的任何数据都需要通过与内存进行交互,那么肯定会降低CPU的执行效率,因此就有了CPU的高速缓存。

2、什么是CPU的高速缓存

CPU中有一定的空间用于存储执行指令所需的临时数据,在程序执行时CPU会先从主内存中将运行所需的数据复制一份到自身的高速缓存中。那么在执行时就只需要从自身的缓存中读取和向其中写入数据,避免了"漫长"的与内存交互的过程。当运算结束之后再将缓存中的数据刷新到主内存中。

以下方代码为例:

int i = 0; // 语句1
i = i + 1; // 语句2
  1. 执行语句1时,会先从主内存中读取i的初始值0,存入CPU的高速缓存中。
  2. CPU执行指令对i进行加1操作。
  3. 将加1后的值写入CPU的高速缓存中。
  4. 最后将数据刷新到主内存中,主内存中的i赋值为1。

3、CPU高速缓存导致的缓存不一致问题

上述代码在单线程中执行是没有问题的,但是到了多线程中就可能会问题。在多核CPU中每个CPU中会同时运行不同的线程,每个线程都会拥有不同的CPU高速缓存(单核CPU也会出现多线程问题,以现在线程调度执行上)。

仍然以上述代码为例,i的初始值为0,当线程1和线程2同时执行语句1时我们期望的最终结果为2。但结果不一定为2,因为可能会出现下面一种情况:

1、线程1和线程2同时读取i的初始值0到自身CPU的高速缓存中。
2、线程1执行加一操作,线程1高速缓存中的i变成1,并将计算的数据刷新到主内存中i为1。
3、线程2执行加一操作,线程2高速缓存中的i变成1,并将计算的数据刷新到主内存中i为1。

最终的结果并不是我们希望的2而是变成了1,这就是缓存一致性问题。上述中被多个线程访问的变量i,通常称为共享变量。通俗的讲就是如果一个变量在多个CPU中都存在缓存,那么就可能会出现缓存一致性问题。

4、如何解决缓存一致性问题

1、总线锁机制。
2、缓存一致性协议。

(1)、总线锁机制

由于CPU与其它组件进行交互都是通过总线来进行的,如果当一个CPU需要访问主内存中的一个共享变量时,采取对对总线加锁的机制,就会阻塞其它CPU与其他组件的交互,从而使得只有当前的CPU能够操作主内存中的共享变量,进而避免了缓存一致性问题。

还是以上述代码为例,当线程1执行语句2时向总线发出加锁的操作信号后,此时就只能有线程1操作共享变量i。线程2就会处于阻塞状态,只有当线程1执行完毕后释放总线锁,线程2才能操作共享变量i。

总线锁机制虽然能够解决缓存一致性问题,但是由于会阻塞其它线程的执行,所以执行效率低下。

(2)、缓存一致性协议

由于总线锁机制的 效率低下,所以出现了缓存一致性协议。其中最著名的就是Intel的MESI协议。

MESI协议保证了每一个CPU缓存中使用的同一共享变量的副本都是一致的。核心理念是:当CPU向内存中写数据时,如果发现操作中使用到了共享变量(在其他CPU中存在该变量的副本),那么就会发出信号通知其它CPU将该变量的缓存行置为无效状态。因此当其他的CPU读取缓存中的这个变量副本时,发现本身缓存中的该变量缓存行为无效状态,那么就会重新去主内存中读取该变量。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值