Java内存模型
Java内存模型:即Java Memory Model,简称JMM
本身是一种抽象的概念,并不真实存在,它描述的是一组规则或规范,通过这组规范定义了程序中各个变量(包括实例字段,静态字段和构成数组对象的元素)在共享数据区域和私有数据区域的访问方式,JMM是围绕原子性,有序性、可见性展开的(稍后会分析)。
jmm与硬件的内存结构很像,之所以定义这样的内存模型,一方面是为了贴合操作系统和硬件的结构,另外一方面也是为了定义一种与平台无关的内存模型,屏蔽不同硬件和操作系统的内存访问差异。
内存模型的平衡点
- 顺序一致性内存模型,是一个理论参考模型,保证模型中的每个操作都是原子性、有序性、可见性的。
- 处理器的内存模型,允许处理器的不同线程缓存区不可见和通过重排序指令来优化程序执行,不同处理器的内存模型也不一样。
- JMM(java内存模型),java内存模型就是对上面两种模型的一种平衡,即要考虑处理器优化,也要保证程序执行安全有序,JMM屏蔽了不同处理器内存模型差异,提供一个一致的内存模型。
八大操作
JMM定义了8种操作来实现主内存与工作内存之间的交互:
(1)lock(锁定):作用于主内存的变量,把一个变量标记为一条线程独占状态
(2)unlock(解锁):作用于主内存的变量,把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定
(3)read(读取):作用于主内存的变量,把一个变量值从主内存传输到线程的工作内存中,以便随后的load动作使用
(4)load(载入):作用于工作内存的变量,它把read操作从主内存中得到的变量值放入工作内存的变量副本中
(5)use(使用):作用于工作内存的变量,把工作内存中的一个变量值传递给执行引擎
(6)assign(赋值):作用于工作内存的变量,它把一个从执行引擎接收到的值赋给工作内存的变量
(7)store(存储):作用于工作内存的变量,把工作内存中的一个变量的值传送到主内存中,以便随后的write的操作
(8)write(写入):作用于工作内存的变量,它把store操作从工作内存中的一个变量的值传送到主内存的变量中
happen-before八大原则
happen-before:是指定两个操作之间的执行顺序的规则,JMM通过happen-before来向程序员日常开发提供跨线程的内存可见性。
happen-before的设计目标:一方面为程序员提供足够的内存可见性保证;另外一方面,对编译器和处理器的限制尽可能放松。最终保证只要不改变程序运行结果(指单线程程序和正确同步的多线程程序),编译器和处理器可以任意优化。
八大原则
- 单线程happen-before原则:在同一个线程中,书写在前面的操作happen-before后面的操作。
- 锁的happen-before原则:同一个锁的unlock操作happen-before此锁的lock操作。
- volatile的happen-before原则:对一个volatile变量的写操作happen-before对此变量的任意操作(当然也包括写操作了)。
- happen-before的传递性原则:如果A操作 happen-before B操作,B操作happen-before C操作,那么A操作happen-before C操作。
- 线程启动的happen-before原则:同一个线程的start方法happen-before此线程的其它方法。
- 线程中断的happen-before原则:对线程interrupt方法的调用happen-before被中断线程的检测到中断发送的代码。
- 线程终结的happen-before原则:线程中的所有操作都happen-before线程的终止检测。
- 对象创建的happen-before原则:一个对象的初始化完成先于他的finalize方法调用。
happen-before和as if serial是类似的,as if serial可以保证单程序的执行结果不会改变,happen-before保证正确同步的多线程程序的执行结果不会改变。
as if serial
数据依赖性:如果两个操作访问同一个变量,且两个操作中有一个为写操作,此时两个操作就存在数据依赖性。
比如a=1和b=a两个操作有数据依赖性。
a=1和b=2没有数据依赖性。
as if serial:不管怎么重排序,单线程的结果不会改变,也就是说,编译器和处理器不会对存在依赖关系的操作进行重排序。
编译器和处理器都遵守as if serial语义。
内存屏障
volatile和final的可见性和禁止重排序都是通过内存屏障指令来实现的。
内存屏障作用
- 保证可见性:强制刷出各种CPU的缓存数据,因此任何CPU上的线程都能读取到这些数据的最新版本
- 禁止指令重排序:通过插入内存屏障禁止在内存屏障前后的指令执行重排序优化
四种内存屏障
- LoadLoad屏障:对于这样的语句Load1; LoadLoad; Load2,在Load2及后续读取操作要读取的数据被访问前,保证Load1要读取的数据被读取完毕。
- StoreStore屏障:对于这样的语句Store1; StoreStore; Store2,在Store2及后续写入操作执行前,保证Store1的写入操作对其它处理器可见。
- LoadStore屏障:对于这样的语句Load1; LoadStore; Store2,在Store2及后续写入操作被刷出前,保证Load1要读取的数据被读取完毕。
- StoreLoad屏障:对于这样的语句Store1; StoreLoad; Load2,在Load2及后续所有读取操作执行前,保证Store1的写入对所有处理器可见。它的开销是四种屏障中最大的。