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