【转】深刻理解Java内存模型(七)——总结

处理器内存模型

顺序一致性内存模型是一个理论参考模型,JMM和处理器内存模型在设计时一般会把顺序一致性内存模型做为参照。JMM和处理器内存模型在设计时会对顺序一致性模型作一些放松,由于若是彻底按照顺序一致性模型来实现处理器和JMM,那么不少的处理器和编译器优化都要被禁止,这对执行性能将会有很大的影响。java

根据对不一样类型读/写操做组合的执行顺序的放松,能够把常见处理器的内存模型划分为下面几种类型:程序员

  1. 放松程序中写-读操做的顺序,由此产生了total store ordering内存模型(简称为TSO)。
  2. 在前面1的基础上,继续放松程序中写-写操做的顺序,由此产生了partial store order 内存模型(简称为PSO)。
  3. 在前面1和2的基础上,继续放松程序中读-写和读-读操做的顺序,由此产生了relaxed memory order内存模型(简称为RMO)和PowerPC内存模型。

注意,这里处理器对读/写操做的放松,是以两个操做之间不存在数据依赖性为前提的(由于处理器要遵照as-if-serial语义,处理器不会对存在数据依赖性的两个内存操做作重排序)。编程

下面的表格展现了常见处理器内存模型的细节特征:缓存

内存模型名称安全

对应的处理器多线程

 

Store-Load 重排序app

Store-Store重排序性能

Load-Load 和Load-Store重排序优化

能够更早读取到其它处理器的写spa

能够更早读取到当前处理器的写

TSO

sparc-TSO

X64

Y

     

Y

PSO

sparc-PSO

Y

Y

   

Y

RMO

ia64

Y

Y

Y

 

Y

PowerPC

PowerPC

Y

Y

Y

Y

Y

在这个表格中,咱们能够看到全部处理器内存模型都容许写-读重排序,缘由在第一章以说明过:它们都使用了写缓存区,写缓存区可能致使写-读操做重排序。同时,咱们能够看到这些处理器内存模型都容许更早读到当前处理器的写,缘由一样是由于写缓存区:因为写缓存区仅对当前处理器可见,这个特性致使当前处理器能够比其余处理器先看到临时保存在本身的写缓存区中的写。

上面表格中的各类处理器内存模型,从上到下,模型由强变弱。越是追求性能的处理器,内存模型设计的会越弱。由于这些处理器但愿内存模型对它们的束缚越少越好,这样它们就能够作尽量多的优化来提升性能。

因为常见的处理器内存模型比JMM要弱,java编译器在生成字节码时,会在执行指令序列的适当位置插入内存屏障来限制处理器的重排序。同时,因为各类处理器内存模型的强弱并不相同,为了在不一样的处理器平台向程序员展现一个一致的内存模型,JMM在不一样的处理器中须要插入的内存屏障的数量和种类也不相同。下图展现了JMM在不一样处理器内存模型中须要插入的内存屏障的示意图:

如上图所示,JMM屏蔽了不一样处理器内存模型的差别,它在不一样的处理器平台之上为java程序员呈现了一个一致的内存模型。

JMM,处理器内存模型与顺序一致性内存模型之间的关系

JMM是一个语言级的内存模型,处理器内存模型是硬件级的内存模型,顺序一致性内存模型是一个理论参考模型。下面是语言内存模型,处理器内存模型和顺序一致性内存模型的强弱对比示意图:

从上图咱们能够看出:常见的4种处理器内存模型比经常使用的3中语言内存模型要弱,处理器内存模型和语言内存模型都比顺序一致性内存模型要弱。同处理器内存模型同样,越是追求执行性能的语言,内存模型设计的会越弱。

JMM的设计

从JMM设计者的角度来讲,在设计JMM时,须要考虑两个关键因素:

  • 程序员对内存模型的使用。程序员但愿内存模型易于理解,易于编程。程序员但愿基于一个强内存模型来编写代码。
  • 编译器和处理器对内存模型的实现。编译器和处理器但愿内存模型对它们的束缚越少越好,这样它们就能够作尽量多的优化来提升性能。编译器和处理器但愿实现一个弱内存模型。

因为这两个因素互相矛盾,因此JSR-133专家组在设计JMM时的核心目标就是找到一个好的平衡点:一方面要为程序员提供足够强的内存可见性保证;另外一方面,对编译器和处理器的限制要尽量的放松。下面让咱们看看JSR-133是如何实现这一目标的。

为了具体说明,请看前面提到过的计算圆面积的示例代码:

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

上面计算圆的面积的示例代码存在三个happens- before关系:

  1. A happens- before B;
  2. B happens- before C;
  3. A happens- before C;

因为A happens- before B,happens- before的定义会要求:A操做执行的结果要对B可见,且A操做的执行顺序排在B操做以前。 可是从程序语义的角度来讲,对A和B作重排序即不会改变程序的执行结果,也还能提升程序的执行性能(容许这种重排序减小了对编译器和处理器优化的束缚)。也就是说,上面这3个happens- before关系中,虽然2和3是必须要的,但1是没必要要的。所以,JMM把happens- before要求禁止的重排序分为了下面两类:

  • 会改变程序执行结果的重排序。
  • 不会改变程序执行结果的重排序。

JMM对这两种不一样性质的重排序,采起了不一样的策略:

  • 对于会改变程序执行结果的重排序,JMM要求编译器和处理器必须禁止这种重排序。
  • 对于不会改变程序执行结果的重排序,JMM对编译器和处理器不做要求(JMM容许这种重排序)。

下面是JMM的设计示意图:

从上图能够看出两点:

  • JMM向程序员提供的happens- before规则能知足程序员的需求。JMM的happens- before规则不但简单易懂,并且也向程序员提供了足够强的内存可见性保证(有些内存可见性保证其实并不必定真实存在,好比上面的A happens- before B)。
  • JMM对编译器和处理器的束缚已经尽量的少。从上面的分析咱们能够看出,JMM实际上是在遵循一个基本原则:只要不改变程序的执行结果(指的是单线程程序和正确同步的多线程程序),编译器和处理器怎么优化都行。好比,若是编译器通过细致的分析后,认定一个锁只会被单个线程访问,那么这个锁能够被消除。再好比,若是编译器通过细致的分析后,认定一个volatile变量仅仅只会被单个线程访问,那么编译器能够把这个volatile变量看成一个普通变量来对待。这些优化既不会改变程序的执行结果,又能提升程序的执行效率。

JMM的内存可见性保证

Java程序的内存可见性保证按程序类型能够分为下列三类:

  1. 单线程程序。单线程程序不会出现内存可见性问题。编译器,runtime和处理器会共同确保单线程程序的执行结果与该程序在顺序一致性模型中的执行结果相同。
  2. 正确同步的多线程程序。正确同步的多线程程序的执行将具备顺序一致性(程序的执行结果与该程序在顺序一致性内存模型中的执行结果相同)。这是JMM关注的重点,JMM经过限制编译器和处理器的重排序来为程序员提供内存可见性保证。
  3. 未同步/未正确同步的多线程程序。JMM为它们提供了最小安全性保障:线程执行时读取到的值,要么是以前某个线程写入的值,要么是默认值(0,null,false)。

下图展现了这三类程序在JMM中与在顺序一致性内存模型中的执行结果的异同:

只要多线程程序是正确同步的,JMM保证该程序在任意的处理器平台上的执行结果,与该程序在顺序一致性内存模型中的执行结果一致。

JSR-133对旧内存模型的修补

JSR-133对JDK5以前的旧内存模型的修补主要有两个:

  • 加强volatile的内存语义。旧内存模型容许volatile变量与普通变量重排序。JSR-133严格限制volatile变量与普通变量的重排序,使volatile的写-读和锁的释放-获取具备相同的内存语义。
  • 加强final的内存语义。在旧内存模型中,屡次读取同一个final变量的值可能会不相同。为此,JSR-133为final增长了两个重排序规则。如今,final具备了初始化安全性。

转自:http://www.infoq.com/cn/articles/java-memory-model-7

相关文章
相关标签/搜索