Java内存模型(即Java Memory Model,简称JMM)本身是一种抽象的概念,并不真实存在,它描述的是一组规则或规范,通过这组规范定义了程序中各个变量(包括实例字段,静态字段和构成数组对象的元素)的访问方式。

基础

问题

线程(并发执行的活动实体)之间的通信同步,Java的并发采用的是共享内存模型

  • 通信:线程之间以何种机制来交换信息

    • 共享内存
      • 通过读-写内存中的公共状态进行隐式通信
    • 消息传递
      • 通过发消息显式通信
  • 同步:程序中用于控制不同线程间操作发生相对顺序的机制

    • 共享内存
      • 显式进行:必须显式指定某个方法或某段代码需要在线程之间互斥执行
    • 消息传递
      • 隐式进行:由于消息的发送必须在消息的接受之前

抽象结构

共享变量:实例域、静态域和数组元素

非共享变量:局部变量、方法定义参数和异常处理器参数

线程之间的共享变量存储在主内存中,每个线程都有一个私有的本地内存,本地内存中存储了该线程以读/写共享变量的副本。

本地内存是JMM的一个抽象概念,涵盖了缓存、写缓冲区、寄存器以及其它的硬件和编译器优化

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

指令重排序

  • 种类
    • 编译器优化的重排序
      • 不改变但线程陈谷语义的前提下,可以重新安排语句的执行顺序
      • JMM的编译器重排序规则会禁止特定类型的编译器重排序
    • 处理器重排序(指令集并行的重排序)
      • 指令集并行技术(Instruction-Level Parallelism, ILP)
      • 如果不存在数据依赖性,处理器可以改变语句对应及其指令的执行顺序
      • 生成指令序列时,插入特定类型的内存屏障指令,禁止特定类型的处理器重排序
    • 内存系统的重排序
      • 由于处理器使用缓存和读/写缓冲区,这使得加载和存储操作看上去可能是在乱序执行

内存屏障

现代的处理器使用写缓冲区临时保存向内存写入的数据

  • 优点
    • 保证指令流水线持续运行
    • 避免由于处理停顿下来等待向内存写入数据而产生的延迟
    • 通过以批处理的方式刷新写缓冲区,以及合并写缓冲区中对同一内存地址的多次写,减少对内存总线的占用
  • 缺点
    • 每个处理器上的写缓冲区,仅仅对它所在的处理器可见
      • 处理器对内存的读/写操作的执行顺序,不一定与内存实际发生的读/写顺序一致

常见的处理器都允许Store_Load重排序;不允许存在数据依赖的操作重排序。

分类

屏障类型 指令实例 说明
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同时具有其它3个屏障的效果。大多处理器都支持。但执行该屏障开销会很昂贵,因为当前处理器通常要把写缓冲区中的数据全部刷新到内存中(Buffer Fully Flush)

happens-before

JDK 5+

在JMM中,如果一个操作(同线程或不同线程)执行的结果需要对另一个操作可见,那么这两个操作之间必须要存在happens-before关系

仅仅要求前一个操作(执行结果)对后一个操作可见

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