先总结:
内存屏障
CPU乱序执行在单线程环境下是一种很好的优化手段,但是在多线程环境下,就会出现数据不一致的问题,因此就可以通过内存屏障这个机制来处理这个问题。
1.写内存屏障(Store Memory Barrier):在指令后插入Store Barrier,能让写入缓存中最新数据更新写入主内存中,让其他线程可见。强制写入主内存,这种显示调用,不会让CPU去进行指令重排序
2.读内存屏障(Load Memory Barrier):在指令后插入Load Barrier,可以让高速缓存中的数据失效,强制重新从主内存中加载数据。也是不会让CPU去进行指令重排。
一、计算机CPU以及多级缓存
现代CPU现在比现代的内存系统快得多。为了弥合这一鸿沟,CPU使用复杂的缓存系统,这些系统可以有效地快速生成硬件哈希表,而无需链接。
下面是一张从美团的技术博客————《高性能队列——Disruptor》 讲解伪共享 时的配图:
- L1、L2、L3分别表示一级缓存、二级缓存、三级缓存,越靠近CPU的缓存,速度越快,容量也越小。
- L1 缓存很小但很快,并且紧靠着在使用它的CPU内核;
- L2 大一些,也慢一些,并且仍然只能被一个单独的CPU核使用;
- L3 更大、更慢,并且被单个插槽上的所有CPU核共享;
- 最后是主存(内存),被全部插槽上的所有CPU核共享。
另外,线程之间共享一份数据的时候,需要一个线程把数据写回主存,而另一个线程访问主存中相应的数据。
二、Memory Barrier
接着就是 《Disruptor Paper》 中 Memory Barriers 一节中的论述:
这些缓存通过消息传递协议与其他处理器缓存系统保持一致。
此外,处理器具有“存储缓冲区”来卸载对这些缓存的写入,以及“使队列失效”,以便缓存一致性协议能够在即将发生写入时快速确认失效消息,从而提高效率。
读内存屏障通过在无效队列中标记一个点来指示 CPU 上的加载指令来执行它,以便更改进入其缓存。 这使它对在读取屏障之前排序的写入操作具有一致的视图。
写屏障通过在存储缓冲区中标记一个点来命令 CPU 上的存储指令执行它,从而通过其缓存刷新写出。 这个屏障提供了一个有序的视图,了解在写入屏障之前发生了什么存储操作。
在Java内存模型中,volatile字段的读写分别实现读写屏障。
三、缓存一致性协议————MESI
MESI 协议是 Cache line 四种状态的首字母的缩写,分别是修改(Modified)态、独占(Exclusive)态、共享(Shared)态和失效(Invalid)态。 Cache 中缓存的每个 Cache Line 都必须是这四种状态中的一种。
3.1 Cache 的状态:
Cache块状态 | 详细解释 | 简要说明 |
---|---|---|
修改态(Modified) | 如果该 Cache Line 在多个 Cache 中都有备份,那么只有一个备份能处于这种状态,并且“dirty”标志位被置上。拥有修改态 Cache Line 的 Cache 需要在某个合适的时候把该 Cache Line 写回到内存中。但是在写回之前,任何处理器对该 Cache Line在内存中相对应的内存块都不能进行读操作。 Cache Line 被写回到内存中之后,其状态就由修改态变为共享态。 | 当前CPU cache拥有最新数据(最新的cache line),其他CPU拥有失效数据(cache line的状态是invalid),虽然当前CPU中的数据和主存是不一致的,但是以当前CPU的数据为准; |
独占态(Exclusive) | 和修改状态一样,如果该 Cache Line 在多个 Cache 中都有备份,那么只有一个备份能处于这种状态,但是“dirty”标志位没有置上,因为它是和主内存内容保持一致的一份拷贝。如果产生一个读请求,它就可以在任何时候变成共享态。相应地,如果产生了一个写请求,它就可以在任何时候变成修改态。 | 只有当前CPU中有数据,其他CPU中没有改数据,当前CPU的数据和主存中的数据是一致的; |
共享态(Shared) | 意味着该 Cache Line 可能在多个 Cache 中都有备份,并且是相同的状态,它是和内存内容保持一致的一份拷贝,而且可以在任何时候都变成其他三种状态。 | 当前CPU和其他CPU中都有共同数据,并且和主存中的数据一致; |
失效态(Invalid) | 该 Cache Line 要么已经不在 Cache 中,要么它的内容已经过时。一旦某个Cache Line 被标记为失效,那它就被当作从来没被加载到 Cache 中; | 当前CPU中的数据失效,数据应该从主存中获取,其他CPU中可能有数据也可能无数据,当前CPU中的数据和主存被认为是不一致的; |
3.2 Cache 的操作:
MESI协议中,每个cache的控制器不仅知道自己的操作(local read和local