JMM 属于语言级的内存模型,它确保在不同的编译器和不同的处理器平台之上,通过禁止特定类型的编译器重排序和处理器重排序,为程序员提供一致的内存可见性保证.
在并发编程中,需要处理两个关键问题:线程之间如何通信及线程之间如何同步.
1. 线程通信
通信是指线程之间以何种机制来交换信息。在命令式编程中,线程之间的通信机制有两种:共享内存和消息传递
共享内存的并发模型里,线程之间共享程序的公共状态,通过写-读内存中的公共状态进行隐式通信
消息传递的并发模型里,线程之间没有公共状态,线程之间必须通过发送消息来显式进行通信
2. 线程同步
即当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到该线程完成操作, 其他线程才能对该内存地址进行操作
共享内存并发模型里,同步是显式进行的。程序员必须显式指定某个方法或某段代码需要在线程之间互斥执行。
消息传递并发模型里,由于消息的发送必须在消息的接收之前,因此同步是隐式进行的
Java 的并发采用的是共享内存模型,Java 线程之间的通信总是隐式进行,整个通信过程对程序员完全透明
3. 内存模型
共享变量
在 Java 中,所有实例域、静态域和数组元素都存储在堆内存中,堆内存在线程之间共享(本章用“共享变量”这个术语代指实例域,静态域和数组元素)。
局部变量(Local Variables
方法定义参数(Java 语言规范称之为 Formal Method Parameters)和异常处理器参数(Exception Handler Parameters)不会在线程之间共享,它们不会有内存可见性问题,也不受内存模型的影响
3.1. 内存模型分类
根据对不同类型的读/写操作组合的执行顺序的放松,可以把常见处理器的内存模型划分为如下几种类型
- 放松程序中写-读操作的顺序,由此产生了 Total Store Ordering 内存模型(简称为 TSO)
- 在上面的基础上,继续放松程序中写-写操作的顺序,由此产生了 Partial Store Order 内存模型(简称为 PSO)
- 在前面两条的基础上,继续放松程序中读-写和读-读操作的顺序,由此产生了 Relaxed Memory Order 内存模型(简称为 RMO)和 PowerPC 内存模型
注意,这里处理器对读/写操作的放松,是以两个操作之间不存在数据依赖性为前提的(因为处理器要遵守 as-if-serial 语义,处理器不会对存在数据依赖性的两个内存操作做重排序)
由于常见的处理器内存模型比 JMM 要弱,Java 编译器在生成字节码时,会在执行指令序列的适当位置插入内存屏障来限制处理器的重排序。同时,由于各种处理器内存模型的强弱不同,为了在不同的处理器平台向程序员展示一个一致的内存模型,JMM 在不同的处理器中需要插入的内存屏障的数量和种类也不相同
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YYT9QT7b-1577707808936)(https://note.youdao.com/yws/public/resource/cddecaa935786d2205d8f5777979a762/xmlnote/D1745BBE6CB34E06AD1A61D07316FC59/41011)]
3.2. JMM 的内存可见性保证
按程序类型,Java 程序的内存可见性保证可以分为下列3类。
- 单线程程序。单线程程序不会出现内存可见性问题。编译器、runtime和处理器会共同确保单线程程序的执行结果与该程序在顺序一致性模型中的执行结果相同
- 正确同步的多线程程序。正确同步的多线程程序的执行将具有顺序一致性(程序的执行结果与该程序在顺序一致性内存模型中的执行结果相同)。这是JMM关注的重点,JMM通过限制编译器和处理器的重排序来为程序员提供内存可见性保证
- 未同步/未正确同步的多线程程序。JMM为它们提供了最小安全性保障:线程执行时读取到的值,要么是之前某个线程写入的值,要么是默认值(0、null、false)