Java多线程12-内存模型

一、 高速缓存
因为主内存执行一次内存读、写操作所需时间可能足够处理器执行上百条的指令,为了弥补差距、引入了高速缓存Cache

高速缓存:是一种存取速率远比内存大而容量远比内存小的存储部件,每个处理器都有其高速缓存
高速缓存相当于为程序所访问的每个变量保留了一份相应内存空间所存储数据(变量值)的副本,由于高速缓存的存储容量远小于主内存,所以高速缓存并不是每时每刻保留着所有变量值的副本。
高速缓存结构:相当于硬件实现的容量极小的散列表(Hash Table),其键(Key)是一个内存地址,其值(Value)是内存数据的副本或者准备写入内存的数据;其内部结构相当于一个拉链散列表(Chained Hash Table),它包含若干桶(Bucket)每个桶又分为若干条目(Cache Entry)


缓存条目:内部划分为Tag,Data Block,Flag这三部分,Data Block又称缓存行(Cache Line)是高速缓存与主内存之间的数据交换最小单元,用于从内存中读取的或者准备写往内存的数据;Tag则包含了缓存行中数据相应的内存地址的部分信息(内存地址的高位部分比特),Flag用于表示相应缓存行的状态信息;


现代处理器一般具有多个层次的高速缓存,如图所示、一级缓存可能直接被集成在处理器的内核(Core)里,因此访问效率非常高,距离处理器越近的高速缓存,其存取速率越快,成本越高、容量越小。

二、 缓存一致性协议
数据世界的交通规则
多线程并发访问同一个共享变量的时候,这些线程的执行处理器上的高速缓存各自都会保留一份该共享变量的副本,随之带来一个新问题:一个处理器对其副本数据进行更新之后,其他处理器如何察觉该更新并做出适当反应,以确保这些处理器后续读取该共享变量时能够读取到这个更新;这就是缓存一致性问题,为解决这问题、建立了通信机制—缓存一致性协议
MESI(Modified-Exculsive-Shared-Invalid)协议为一种广为使用的缓存一致性协议,它使得针对同一地址的读内存操作是并发的,而针对同一地址的写内存操作是独占的
三、 写缓冲器与无效化队列
MESI协议解决了缓存一致性问题,但新的问题也产生了:处理器执行写内存操作时,必须等待其他所有处理器将其高速缓存中的相应副本数据删除并接收到这些处理器所回复的Invalidate Acknowledge/Read Response消息之后才能将数据写入高速缓存;为规避和减少这种等待造成的写操作延迟,引入了写缓冲器和无效化队列
写缓冲器(Store Buffer)是处理器内部的一个容量比高速缓存还小的私有高速存储部件,每个处理器都有其写缓冲器,写缓冲器内部可包含若干条目(Entry)一个处理器无法读取另一处理器上的写缓冲器内容


写缓冲器的引入:使得处理器在执行写操作的时候可以不等待Invalidate Acknowledge消息,从而减少了写操作延时,使得写操作的处理器在其他处理器回复Invalidate Acknowledge/Read Response消息这段时间内能够执行其他指令,从而提高处理器指令执行效率
无效化队列的引入:处理器在接收到Invalidate消息之后并不删除消息中指定地址对应的副本数据,而是将消息存入无效化队列之后就回复Invalidate Acknowledge消息,从而减少写操作执行处理器所需等待时间
写缓冲器和无效化队列的引入又会带来新的问题:内存重排序、可见性问题
存储转发:处理器直接从写缓冲器中读取数据来实现内存读操作
内存屏障(Store Barrier):解决内存重排序问题的特殊指令
加载屏障(Load Barrier):解决可见性问题

四、 基本内存屏障
LoadLoad屏障、LoadStore屏障、StoreStore屏障、StoreLoad屏障

五、 Java同步机制与内存屏障
Java虚拟机对synchronized、volatile和final关键字的语义实现就是借助内存屏障
复合屏障:又基本内存屏障组合而成
获取屏障:LoadLoad屏障和LoadStore屏障的组合,能够禁止该屏障之前的任何读操作与该屏障之后的任何读、写操作之间进行重排序
释放屏障:LoadStore屏障和StoreStore屏障组合,能够禁止该屏障之前的任何读、写操作与该屏障之后的任何写操作之间进行重排序
volatile关键字的实现
Java虚拟机在volatile变量写操作之前插入的释放屏障使得该屏障之前的任何读、写操作都先于这个volatile变量写操作被提交,而Java虚拟机在volatile变量读操作之后插入的获取屏障使得这个volatile变量读操作先于该屏障之后的任何读、写操作被提交。
Synchronized关键字的实现
Java虚拟机会在monitorenter(用于申请锁的字节码指令)对应的指令后临界区开始前的地方插入一个获取屏障;Java虚拟机会在临界区结束后的monitorenter(用于释放锁的字节码指令)对应的指令前的地方插入一个释放屏障。获取屏障和释放屏障一起保证了临界区的操作具有原子性。
final关键字的实现
Java虚拟机在插入StoreStore屏障后保障编译器不将final字段的初始化操作重排序
内存屏障部分禁止重排序的代价就是它会阻止编译器、处理器做一些性能优化,另一个涉及的冲刷写缓冲器和清空无效化队列的动作比价耗时,所以Java虚拟机会做一些优化:省略、合并等

更多内容,请关注微信公众号: