Java并发(二):Java内存模型

1、硬件内存架构

一个现代计算机一般由两个或者多个CPU。其中一些CPU还有多核。每一个CPU在某一时刻运行一个线程是没有问题的。若是你的Java程序是多线程的,在你的Java程序中每一个CPU上一个线程可能同时(并发)执行。html

当一个CPU须要读取主存时,它会将主存的部分读到CPU缓存中。它甚至可能将缓存中的部份内容读到它的内部寄存器中,而后在寄存器中执行操做。java

当CPU须要将结果写回到主存中去时,它会将内部寄存器的值刷新到缓存中,而后在某个时间点将值刷新回主存。程序员

2、并发编程的问题

并发编程,为了保证数据的安全,须要知足如下三个特性:编程

原子性:在一个操做中就是cpu不能够在中途暂停而后再调度,既不被中断操做,要不执行完成,要不就不执行。(处理器优化)数组

  原子性问题:线程在执行一个读改写操做时,在执行读改以后,时间片耗完,就会被要求放弃CPU,并等待从新调度。此时另外一个线程对同一个变量执行读改写操做就会出现问题。这种状况下,读改写就不是一个原子操做。缓存

i = 0;      // 基本数据类型的变量和赋值操做都是原子性操做
j = i ;     // 包含了两个操做:读取i,将i值赋值给j 
i++;         // 包含了三个操做:读取i值、i + 1 、将+1结果赋值给i
i = j + 1;  // 包含了三个操做:读取j值、j + 1 、将+1结果赋值给i

  在单线程环境下咱们能够认为整个步骤都是原子性操做。可是在多线程环境下则不一样,Java只保证了基本数据类型的变量和赋值操做才是原子性的。要想在多线程环境下保证原子性,则能够经过锁、synchronized来确保。安全

可见性:当多个线程访问同一个变量时,一个线程修改了这个变量的值,其余线程可以当即看获得修改的值。(缓存一致性问题)多线程

有序性:程序执行的顺序按照代码的前后顺序执行。(指令重排)架构

内存模型经过限制处理器优化和使用内存屏障,来保证共享内存的正确性(可见性、有序性、原子性)。并发

Java内存模型(Java Memory Model ,JMM)就是一种符合内存模型规范的,屏蔽了各类硬件和操做系统的访问差别的,保证了Java程序在各类平台下对内存的访问都能保证效果一致的机制及规范。

JMM还经过volatilesynchronizedfinalconcurren包等实现原子性、有序性、可见性。

3、Java内存模型(JMM)

共享变量:堆内存在线程之间共享,存储在堆内存中全部实例域、静态域和数组元素共享变量

  (局部变量,方法定义参数、异常处理器参数不会在线程之间共享,不会有内存可见性问题,不受内存模型的影响)

JMM定义了线程和主内存之间的抽象关系:

  1)线程之间的共享变量存储在主内存(main memory)中

  2)每一个线程都有一个私有的本地内存(local memory),本地内存中存储了该线程用以读/写共享变量的副本

  3)本地内存是JMM的一个抽象概念,并不真实存在。它涵盖了缓存,写缓冲区,寄存器以及其余的硬件和编译器优化

线程A与线程B通讯:

  1)线程A把本地内存A中更新过的共享变量刷新到主内存中去

  2)线程B到主内存中去读取线程A以前已更新过的共享变量

JMM经过控制主内存与每一个线程的本地内存之间的交互,提供内存可见性保证

JMM的设计

1)常见的处理器内存模型比JMM要弱,java编译器在生成字节码时,会在执行指令序列的适当位置插入内存屏障来限制处理器的重排序。

2)因为各类处理器内存模型的强弱并不相同,为了在不一样的处理器平台向程序员展现一个一致的内存模型,JMM在不一样的处理器中须要插入的内存屏障的数量和种类也不相同。

程序员但愿:强内存模型编程,易于理解,易于编程

编译器和处理器但愿:弱内存模型,内存模型对它们的束缚越少越好,以提升性能

JMM时的核心目标就是找到一个好的平衡点:一方面要为程序员提供足够强的内存可见性保证;另外一方面,对编译器和处理器的限制要尽量的放松。

JMM把happens- before要求禁止的重排序分为了下面两类:

1)会改变程序执行结果的重排序,JMM要求编译器和处理器必须禁止这种重排序。

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

  只要不改变程序的执行结果(指的是单线程程序和正确同步的多线程程序),编译器和处理器怎么优化都行。

  好比,若是编译器通过细致的分析后,认定一个锁只会被单个线程访问,那么这个锁能够被消除。

  再好比,若是编译器通过细致的分析后,认定一个volatile变量仅仅只会被单个线程访问,那么编译器能够把这个volatile变量看成一个普通变量来对待。

  这些优化既不会改变程序的执行结果,又能提升程序的执行效率。

4、顺序一致性内存模型

顺序一致性内存模型是一个被计算机科学家理想化了的理论参考模型,它为程序员提供了极强的内存可见性保证(JMM没有顺序一致性内存模型保证)

特性:

  • 一个线程中的全部操做必须按照程序的顺序来执行。
  • (无论程序是否同步)全部线程都只能看到一个单一的操做执行顺序。在顺序一致性内存模型中,每一个操做都必须原子执行且马上对全部线程可见。

视图信息:

1.顺序一致性模型有一个单一的全局内存

2.在任意时间点最多只能有一个线程能够链接到内存

3.每个线程必须按程序的顺序来执行内存读/写操做

举例:

线程A:A1->A2->A3  线程B:B1->B2->B3  并发执行

正确同步:

两个线程没有作同步:

能够看出:

1.每一个线程内部执行顺序 都是按照程序的顺序来执行

2.全部线程都只能看到一个一致的总体执行顺序(缘由:顺序一致性内存模型中的每一个操做必须当即对任意线程可见)

顺序一致性模型与JMM区别:

  顺序一致性模型保证单线程内的操做会按程序的顺序执行,JMM不保证单线程内的操做会按程序的顺序执行(遵照as-if-serial语义)

  顺序一致性模型保证全部线程只能看到一致的操做执行顺序,而JMM不保证全部线程能看到一致的操做执行顺序

JMM在具体实现上的基本方针:在不改变(正确同步的)程序执行结果的前提下,尽量的为编译器和处理器的优化打开方便之门。 

正确同步,JMM保证程序的执行结果将与该程序在顺序一致性模型中的执行结果相同(但不保证执行顺序)

 

假设A线程执行writer()方法后,B线程执行reader()方法

5、处理器内存模型

若是彻底按照顺序一致性模型来实现,那么不少的处理器和编译器优化都要被禁止,这对执行性能将会有很大的影响。

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

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

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

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

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

JMM是一个语言级的内存模型,处理器内存模型是硬件级的内存模型,顺序一致性内存模型是一个理论参考模型。

语言内存模型,处理器内存模型和顺序一致性内存模型的强弱对比示意图:

内存模型越强,越容易保证内存可见性,易编程性就越好。可是重排序就会越少,执行效率就越低。

 

重排序 :Java并发(三):重排序

happens-before:Java并发(四):happens-before

volatile:Java并发(六):volatile的实现原理

Final:Java并发(十九):final实现原理

 

 

 参考资料:

《成神之路-基础篇》JVM——Java内存模型 

细说Java多线程以内存可见性

Java内存模型FAQ

深刻理解Java内存模型

相关文章
相关标签/搜索