Java内存模型 - 简介 (2)

  为了保证内存可见性,Java编译器在生成指令序列时的适当位置会插入内存屏障指令来禁止特定类型的处理器重排序。JMM把内存屏障分为4类,如下表所示。

屏障类型   指令示例   说明  
LoadLoad Barriers   Load1;LoadLoad;Load2  

确保Load1数据的装载先于Load2及所有后续装载指令的装载。

 
StoreStore Barriers   Store1; StoreStore;Store2  

确保Store1数据对其他处理器可见(刷新到内存)先于Store2

及后续所有存储指令。

 
LoadStore Barriers   Load1;LoadStore;Store2  

确保Load1数据装载先于Store2及所有后续的存储指令刷新到内存。

 
StoreLoad Barriers  

Store1;StoreLoad;Load2

 

确保Store1数据对其他处理器可见(刷新到内存)先于Load2及所有

后续装载指令的装载。

StoreLoad Barriers会使该内存屏障之前的所有内存访问指令(存储

和装载)完成之后,才执行该屏障之后的内存访问指令。

 

 

并发编程模型

  由于计算机的存储设备和处理器的运算速度有着几个量级的差距,所以现代计算机系统加入一层或者多层读写速度尽可能接近处理器速度的高速缓存(Cache)来作为内存与处理器之间的缓存。写缓冲区可以保证指令流水线持续运行,它避免由于处理器停顿下来等待向内存写入数据而产生的延迟。同时,通过批处理的方式刷新写缓冲区,以及合并写缓冲区对同一内存地址的多次写,减少对内存总线的占用。

  高速缓存虽然解决了处理器与内存速度之间的矛盾,但是引入了新的问题:缓存一致性(Cache Chherence)。

  下面用一个例子来具体说明:

    处理器A   处理器B  
代码  

a = 1; //A1

x = b; //A2

 

b = 2; //B1

y = a; //B2

 
运行结果  

初始状态:a = b = 0

处理器允许执行后得到结果: x = y = 0

 

  假设有处理器A和处理器B按照程序顺序并行执行内存访问,最终可能得到 x = y = 0 的结果。具体原因如下图所示。

Java内存模型 - 简介

  这里处理器A和处理器B可以同时把共享变量写入自己的写缓冲区(A1,B1),然后从内存中读取另一个共享变量(A2,B2),最后才把自己写缓冲区里中保存的脏数据刷新到内存中(A3,B3)。当以这种时序执行时,程序就有可能得到 a = b = 0 的结果。

  从内存操作的实际发生顺序来看,直到处理器A执行A3来刷新自己的写缓冲区,写操作A1才算真正执行完成。虽然处理器A执行内存操作的顺序为A1 → A2,但内存操作实际发生顺序却是A2 → A1。此时处理器A的内存操作顺序被重排序了(处理器B的情况一样)。

as-if-serial 语义

  as-if-serial语义的意思是:不管怎么重排序(编译器和处理器为了提高并行度),(单线程)程序的执行结果不能被改变。编译器和处理器都必须遵守as-if-serial语义。

  为了遵守as-if-serial语义,编译器和处理器不会对存在数据依赖的操作做重排序,因为这种操作会改变执行结果。但是如果操作之间不存在数据依赖关系,这些操作就有可能被重排序。下面举例说明。

  

1 double pi = 3.14; // A 2 double r = 1.0; // B 3 double area = pi * r * r; // C

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wssjxy.html