Java的多线程并发出现缓存一致性问题之拙见

Java的多线程并发出现缓存一致性问题

       (如有错误,请不吝赐教)

        首先我们得知道什么是缓存,并明白什么是缓存,缓存是介于物理存储与CPU处理之间的一段内存空间,主要用于存储从物理存储读出、或者要写入的数据,这需要硬件或者软件支持。如果读取或写入物理存储中的一个字节或一段数据,如果没有缓存,那么每次的读写请求都会直接访问物理存储,而物理存储的速度一般都比较慢,而且物理定位也比较慢,缓存使用后,可以一次性读出需要的数据相邻的数据,暂时存储在缓存中,下面如果还要读取,而这部分数据已经在缓存了,就不需要再去读取物理存储,同样,如果是写操作,可以先将需要写入的数据暂时保存在缓存中,等到缓存过期或者强行清空时,再一次写入物理存储。这样可以把多次的物理存储访问,变成一次物理存储的访问,提高访问效率。

      缓存的一致性就是指缓存中的数据是否和目标存储中的数据是一样的,也就是说缓存中已经修改得数据是否已经保存到了物理存储中,物理存储中已经被修改得内容,是否与缓存的内容是一样的。这就是一致性的概念。

         而多线程中出现的缓存一致性问题我们用一个例子来说明:

                        例如 i = i + 1;

     当线程执行这个语句时,会先从主存当中读取i的值,然后复制一份到高速缓存当中(关于高速缓存是什么,自行Google吧),然后CPU执行指令对i进行加1操作,然后将数据写入高速缓存,最后将高速缓存中i最新的值刷新到主存当中。

  这个代码在单线程中运行是没有任何问题的,但是在多线程中运行就会有问题了。在多核CPU中,每条线程可能运行于不同的CPU中,因此每个线程运行时有自己的高速缓存(对单核CPU来说,其实也会出现这种问题,只不过是以线程调度的形式来分别执行的)。本文我们以多核CPU为例。

  比如同时有2个线程执行这段代码,假如初始时i的值为0,那么我们希望两个线程执行完之后i的值变为2。但是事实会是这样吗?

  可能存在下面一种情况:初始时,两个线程分别读取i的值存入各自所在的CPU的高速缓存当中,然后线程1进行加1操作,然后把i的最新值1写入到内存。此时线程2的高速缓存当中i的值还是0,进行加1操作之后,i的值为1,然后线程2把i的值写入内存。

  最终结果i的值是1,而不是2。虽然执行了两次对i的操作,但是输出却只加了1。这就是著名的缓存一致性问题。通常称这种被多个线程访问的变量为共享变量。

       既然出现了缓存一致性的问题,那怎么去解决呢???

       目前通常用在硬件层面解决有两种方法:

       1)是通过在总线加lock#锁的方式

       2)是通过缓存一致性协议  

       在早期的CPU当中,是通过在总线上加LOCK#锁的形式来解决缓存不一致的问题。因为CPU和其他部件进行通信都是通过总线来进行的,如果对总线加LOCK#锁的话,也就是说阻塞了其他CPU对其他部件访问(如内存),从而使得只能有一个CPU能使用这个变量的内存。比如上面例子中 如果一个线程在执行 i = i +1,如果在执行这段代码的过程中,在总线上发出了LCOK#锁的信号,那么只有等待这段代码完全执行完毕之后,其他CPU才能从变量i所在的内存读取变量,然后进行相应的操作。这样就解决了缓存不一致的问题。

  但是上面的方式会有一个问题,由于在锁住总线期间,其他CPU无法访问内存,导致效率低下。

  所以就出现了缓存一致性协议。最出名的就是Intel 的MESI协议,MESI协议保证了每个缓存中使用的共享变量的副本是一致的。它核心的思想是:当CPU写数据时,如果发现操作的变量是共享变量,即在其他CPU中也存在该变量的副本,会发出信号通知其他CPU将该变量的缓存行置为无效状态,因此当其他CPU需要读取这个变量时,发现自己缓存中缓存该变量的缓存行是无效的,那么它就会从内存重新读取,这样就避免了出现缓存一致性问题。

      从其他地方盗来的一张图:

猜你喜欢

转载自blog.csdn.net/hrw1234567890/article/details/81076941