Java内存模型

  • 重排序
  • 数据依赖性
  • as-if-serial语义
  • 编译器优化的重排序
  • 指令级并行的重排序
  • 内存系统的重排序
  • happens-before规则
  • 顺序一致性
  • 同步程序在两个模型的执行
  • 未同步程序在两个模型的执行


Java的并发采用的是共享内存模型,Java线程之间的通信总是隐式进行。 Java线程之间的通信由Java内存模型控制,JMM决定一个线程对共享变量的写入何时对另一个线程可见。

从抽象角度来看,JMM定义了线程和主内存之间的抽象关系:线程之间的共享变量存储在主内存中,每个线程都有一个私有的本地内存,本地内存中存储了该线程以读/写共享变量的副本。本地内存是JMM的一个抽象概念,并不真实存在。它涵盖了缓存、写缓存区、寄存器以及其他的硬件和编译器优化

JAVA内存模型如图所示。

java 内存共享 面试 java内存线程共享_java 内存共享 面试

JMM通过控制主内存与每个线程之间的本地内存之间的交互,来为JAVA程序员提供内存可见性保证。

重排序

指编译器和处理器为了优化程序性能而对指令序列进行重新排序的一种手段。

数据依赖性

如果两个操作访问同一个变量,且这两个操作中有一个为写操作,此时这两个操作之间就存在数据依赖性。数据依赖有写后读,写后写,读后写这3种类型。
编译器和处理器可能对操作进行重排序。但不会改变存在数据依赖关系的两个操作的执行顺序。
数据依赖性仅针对单个处理器中执行的指令序列很单个线程中执行的操作,不同处理器之间和不同线程之间的数据依赖性不被编译器和处理器考虑。

as-if-serial语义

不管怎么重排序,(单线程)程序的执行结果不能被改变。编译器和处理器都必须遵守

编译器优化的重排序

编译器在不改变单线程程序语义的前提下,可以重新安排语句的执行顺序。

指令级并行的重排序

如果不存在数据依赖性,处理器可以改变语句对应机器指令的执行顺序。

内存系统的重排序

由于处理器使用缓存和读写缓冲区,使得加载和存储操作看上去可能是乱序执行。

JMM属于语言级的内存模型,它确保在不同的编译器和不同的处理器平台之上,通过禁止特定类型的编译器重排序由和处理器重排序,为程序提供一致的内存可见性保证。

happens-before规则

阐述操作之间的内存可见性。这两个操作既可以在一个线程内,也可以在不同线程之间。

  • 程序顺序规则
    一个线程的每个操作,happens-before于该线程中的任意后续操作。
  • 监视器锁规则
    对一个锁的解锁,happens-before于随后对这个锁的加锁操作。
  • volatile规则
    对一个volatile域的写,happens-before于任意后续对这个volatile域的读。
  • 传递性
    如果A happens-before B,且B happens-before C,那么A happens-before C。

两个操作之间具有happens-before关系,并不意味着前一个操作必须在后一个操作之前执行,happens-before仅仅要求前一个操作对后一个操作可见,且前一个操作按顺序排在第二个操作之前,

顺序一致性

如果程序是正确同步的(同步指广义上的同步,包括对常用同步原语volatile,final,synchronized的正确使用),程序的执行将具有顺序一致性–即程序的执行结果与该程序在顺序一致性内存模型中的执行结果相同。

  1. 一个线程中的所有操作必须按照程序的顺序来执行
  2. 所有线程都只能看到一个单一的操作执行顺序。在顺序一致性内存模型中,每个操作都必须原子执行且立刻对所有线程可见。

同步程序在两个模型的执行

顺序一致性模型中,所有操作完全按程序串行执行

JMM模型中,临界区代码内的代码可以重排序,提高了执行效果,又没有改变程序的执行结果。

java 内存共享 面试 java内存线程共享_java 内存共享 面试_02

未同步程序在两个模型的执行

JMM模型不保证未同步程序的执行结果与该程序在顺序一致性模型中的执行结果一致。两者在未同步程序的执行差异如下:

  1. 顺序一致性模型中保证所以单线程内的操作会按程序的顺序执行,而JMM不保证单线程内的操作会按程序的顺序执行
  2. 顺序一致性模型保证所有线程只能看到一致的操作执行顺序,而JMM不保证所有线程能看到一致的操作执行顺序。
  3. JMM不保证对64位的long型/double型变量的写操作具有原子性,而顺序一致性模型保证对所有的内存读/写操作具有原子性