GitHub 2.2k Star 的Java工程师成神之路 ,不来了解一下吗?git
GitHub 2.2k Star 的Java工程师成神之路 ,真的不来了解一下吗?程序员
GitHub 2.2k Star 的Java工程师成神之路 ,真的肯定不来了解一下吗?github
在再有人问你Java内存模型是什么,就把这篇文章发给他这篇文章中,咱们介绍过关于Java内存模型的前因后果。编程
咱们在文章中提到过,因为CPU和主存的处理速度上存在必定差异,为了匹配这种差距,提高计算机能力,人们在CPU和主存之间增长了多层高速缓存。每一个CPU会有L一、L2甚至L3缓存,在多核计算机中会有多个CPU,那么就会存在多套缓存,那么这多套缓存之间的数据就可能出现不一致的现象。为了解决这个问题,有了内存模型。内存模型定义了共享内存系统中多线程程序读写操做行为的规范。经过这些规则来规范对内存的读写操做,从而保证指令执行的正确性。缓存
不知道小伙伴们有没有想过这样的问题:内存模型究竟是怎么保证缓存一致性的呢?多线程
接下来咱们试着回答这个问题。首先,缓存一致性是因为引入缓存而致使的问题,因此,这是不少CPU厂商必须解决的问题。为了解决前面提到的缓存数据不一致的问题,人们提出过不少方案,一般来讲有如下2种方案:并发
一、经过在总线加
LOCK#
锁的方式。异步二、经过缓存一致性协议(Cache Coherence Protocol)。编程语言
在早期的CPU当中,是经过在总线上加LOCK#
锁的形式来解决缓存不一致的问题。由于CPU和其余部件进行通讯都是经过总线来进行的,若是对总线加LOCK#
锁的话,也就是说阻塞了其余CPU对其余部件访问(如内存),从而使得只能有一个CPU能使用这个变量的内存。在总线上发出了LCOK#
锁的信号,那么只有等待这段代码彻底执行完毕以后,其余CPU才能从其内存读取变量,而后进行相应的操做。这样就解决了缓存不一致的问题。post
可是因为在锁住总线期间,其余CPU没法访问内存,会致使效率低下。所以出现了第二种解决方案,经过缓存一致性协议来解决缓存一致性问题。
缓存一致性协议(Cache Coherence Protocol),最出名的就是Intel 的MESI协议,MESI协议保证了每一个缓存中使用的共享变量的副本是一致的。
MESI的核心的思想是:当CPU写数据时,若是发现操做的变量是共享变量,即在其余CPU中也存在该变量的副本,会发出信号通知其余CPU将该变量的缓存行置为无效状态,所以当其余CPU须要读取这个变量时,发现本身缓存中缓存该变量的缓存行是无效的,那么它就会从内存从新读取。
在MESI协议中,每一个缓存可能有有4个状态,它们分别是:
M(Modified):这行数据有效,数据被修改了,和内存中的数据不一致,数据只存在于本Cache中。
E(Exclusive):这行数据有效,数据和内存中的数据一致,数据只存在于本Cache中。
S(Shared):这行数据有效,数据和内存中的数据一致,数据存在于不少Cache中。
I(Invalid):这行数据无效。
关于MESI的更多细节这里就不详细介绍了,读者只要知道,MESI是一种比较经常使用的缓存一致性协议,他能够用来解决缓存之间的数据一致性问题就能够了。
可是,值得注意的是,传统的MESI协议中有两个行为的执行成本比较大。
一个是将某个Cache Line标记为Invalid状态,另外一个是当某Cache Line当前状态为Invalid时写入新的数据。因此CPU经过Store Buffer和Invalidate Queue组件来下降这类操做的延时。
如图:
当一个CPU进行写入时,首先会给其它CPU发送Invalid消息,而后把当前写入的数据写入到Store Buffer中。而后异步在某个时刻真正的写入到Cache中。
当前CPU核若是要读Cache中的数据,须要先扫描Store Buffer以后再读取Cache。
可是此时其它CPU核是看不到当前核的Store Buffer中的数据的,要等到Store Buffer中的数据被刷到了Cache以后才会触发失效操做。
而当一个CPU核收到Invalid消息时,会把消息写入自身的Invalidate Queue中,随后异步将其设为Invalid状态。
和Store Buffer不一样的是,当前CPU核心使用Cache时并不扫描Invalidate Queue部分,因此可能会有极短期的脏读问题。
因此,为了解决缓存的一致性问题,比较典型的方案是MESI缓存一致性协议。
MESI协议,能够保证缓存的一致性,可是没法保证明时性。
前面介绍过了缓存一致性模型,接着咱们再来看一下内存模型。咱们说过内存模型定义一系列规范,来保证多线程访问共享变量时的可见性、有序性和原子性。(更多内容请参考再有人问你Java内存模型是什么,就把这篇文章发给他)
内存模型(Memory Model)若是扩展开来讲的话,一般指的是内存一致性模型(Memory Sequential Consistency Model)
前面咱们提到过缓存一致性,这里又要说内存一致性,不是故意要把读者搞蒙,而是但愿经过对比让读者更加清楚。
缓存一致性(Cache Coherence),解决是多个缓存副本之间的数据的一致性问题。
内存一致性(Memory Consistency),保证的是多线程程序访问内存时能够读到什么值。
咱们首先看如下程序:
初始:x=0 y=0
Thread1:
S1:x=1
L1:r1=y
Thread2:
S2:y=2
L2:r2=x
复制代码
其中,S一、S二、L一、L2是语句代号(S表示Store,L表示Load);r1和r2是两个寄存器。x和y是两个不一样的内存变量。两个线程执行完以后,r1和r2多是什么值?
注意到线程是并发、交替执行的,下面是可能的执行顺序和相应结果:
S1 L1 S2 L2 那么r1=0 r2=2
S1 S2 L1 L2 那么r1=2 r2=1
S2 L2 S1 L1 那么r1=2 r2=0
复制代码
这些都是意料以内、情理之中的。可是在x86体系结构下,极可能获得r1=0 r2=0这样的结果。
若是没有Memory Consistency
,程序员写的程序代码的输出结果是不肯定的。
所以,Memory Consistency
就是程序员(编程语言)、编译器、CPU间的一种协议。这个协议保证了程序访问内存时会获得什么值。
简单点说,内存一致性,就是保证并发场景下的程序运行结果和程序员预期是同样的(固然,要经过加锁等方式),包括的就是并发编程中的原子性、有序性和可见性。而缓存一致性说的就是并发编程中的可见性。
在不少内存模型的实现中,关于缓存一致性的保证都是经过硬件层面缓存一致性协议来保证的须要注意的是,这里提到的内存模型,是计算机内存模型,而非Java内存模型。
缓存一致性问题。硬件层面的问题,指的是因为多核计算机中有多套缓存,各个缓存之间的数据不一致性问题。
PS:这里还须要再重复一遍,Java多线程中,每一个线程都有本身的工做内存,须要和主存进行交互。这里的工做内存和计算机硬件的缓存并非一回事儿,只是能够相互类比。因此,并发编程的可见性问题,是由于各个线程之间的本地内存数据不一致致使的,和计算机缓存并没有关系。
缓存一致性协议。用来解决缓存一致性问题的,经常使用的是MESI协议。
内存一致性模型。屏蔽计算机硬件问题,主要来解决并发编程中的原子性、有序性和一致性问题。
实现内存一致性模型的时候可能会用到缓存一致性模型。
最后,再给你们留一道思考题:
既然在硬件层面,已经有了缓存一致性协议,能够保证缓存的一致性即并发编程中的可见性,那么为何在写多线程的代码的时候,程序员要本身使用volatile、synchronized等关键字来保证可见性?
关于这个思考题的答案,我会在接下来的深刻介绍volatile文章中解答。欢迎你们关注个人博客(www.hollischuang.com)和公众号(Hollis)第一时间学习。
Memory Consistency和Cache Coherence
有道无术,术可成;有术无道,止于道;欢迎关注【Java之道】公众号,一块儿以道御术,以术识道;