主内存和工作内存
Java内存模型的主要目的是定义程序中各种变量的访问规则,即关注在虚拟机中把变量值存储内存和从内存中取出这样底层的细节。Java内存模型规定所有的变量都存储在主内存中,每条线程都有自己的工作内存,线程的工作内存保存了该线程使用变量主内存的副本,线程对变量的所有操作都必须在工作内存中进行,而不能直接读写主内存中的数据。不同的线程之间无法直接访问对方工作内存中的变量,线程间的变量传递均需要通过主内存来完成。这里的主内存主要指的是Java堆中的对象实例数据部分,而工作内存主要指的是虚拟机栈中的部分区域。从更基础的层次上讲,主内存直接对应于物理硬件的内存,而为了获取更好运行速度,虚拟机(或者是硬件,操作系统本身的优化措施)可能会让工作内存优先存储于寄存器和高速缓存中,因为程序运行时主要访问的是工作内存。
内存间的交互操作
- lock(锁定): 作用于主内存变量,它把一个变量标识为一条线程独占的状态。
- unlock(解锁): 作用于主内存变量,它把一个处于锁定状态的变量释放出来,释放后的变量才可以被其它线程锁定。
- read(读取): 作用于主内存变量,它把一个变量从主内存传输到县城的工作内存中,以便随后的load动作的使用。
- load(载入):作用于工作内存变量,它把read操作从主内存中得到的变量值放入工作内存的变量副本中。
- use(使用): 作用于工作内存变量 ,它把工作内存变量中一个变量的值传递给执行引擎,每当虚拟机遇到一个需要给变量赋值的字节码指令时将会执行这个操作。
- assign(赋值): 作用于工作内存变量,它把一个从执行引擎接收的值赋给工作内存变量,每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作。
- store(存储): 作用于工作内存变量,它把工作内存中的一个变量的值传送到主内存中,以便随后的write操作的使用。
- write(写入):作用于主内存变量,它把store操作从工作内存中得到的变量的值放入到主内存的变量中。
Java内存模型还规定了在执行上述8中基本操作必须满足如下规则:
- 不允许read和load、store和write操作之一单独出现,即不允许一个变量从主内存中读取了但是内存不接受,或者工作内存发起回写了但主内存不接受的情况出现。
- 不允许一个线程丢弃它最近的assign操作,即变量在工作内存中改变了之后必须把改变化同步回主内存。
- 不允许一个线程无原因的(没有发生任何assign操作)把数据从工作内存同步回主内存。
- 一个新的变量只能值主内存中诞生,不允许在工作内存中直接使用一个未被初始化(load或assign)的变量。换句话说就是对一个变量实施use、store操作之前,必须先执行assign和load操作。
- 一个变量在同一时刻只允许一条线程对其进行lock操作。但lock可以被同一个线程重复执行多次,多次执行lock后,只有执行相同发次数的unlock操作,变量才会被解锁。
- 如果对一个变量执行lock操作,那么将会清空工作内存中此变量的值,在执行引擎使用这个变量前,需重新执行load或assign操作来初始化变量。
- 如果一个变量事先没有被lock操作锁定,那就不允许对它执行unlock操作,也不允许去unlock一个被其他线程锁定的变量。
- 对一个变量执行unlock之前,必须把此变量同步回主内存中(执行store、write操作)。
java内存模型抽象
java的所有实例域、静态域和数组元素都存储在堆内存中,堆内存在线程之间共享
局部变量、方法定义参数和异常处理参数不会再线程之间共享,不会存在内存可见性问题,也不受内存模型的影响。
线程之间的共享变量存储在主内存中,每个线程都有一个本地内存,本地内存存储了该线程以读/写共享变量的副本
指令重排序
包含编译器优化重排序、指令集并行重排序和内存系统重排序
java内存模型通过插入特定类型的内存屏障指令,来禁止特定类型处理器的重排序
每个处理器都有写缓冲区,仅对它所在的处理器可见,处理器对内存的读/写操作的执行顺序,不一定是内存实际发生的读/写操作顺序的一致。只有把数据写到内存中才有可能被其它处理器可见。其它处理器如果已缓存该共享变量,必须通过缓存一致性规则实现可见性。
内存屏障类型
LoadLoad Barriers 内存屏障之前的load要先于内存屏障之后的load
StoreStore Barriers 内存屏障之前的store要先于内存屏障之后的store
LoadStore Barriers 内存屏障之前的load要先于内存屏障之后的store
StoreLoad Barriers 内存屏障之前的store要优先于内存屏障之后的load,会使该内存屏障之前的所有内存访问指令完成之后,才执行内存屏障之后的内存访问命令
happens-before
程序顺序规则:一个线程中的每个操作,happens-before于线程中的任意后续操作
监视器锁定规则:对一个锁的解锁,happens-before于随后对这个锁的加锁
volatile变量规则:对一个volatile域的写,happens-before于任意后续对这个volatile域的读
传递性:如果A happens-before B 且 B happens-before C那么 A happend-before C
数据依赖性
数据依赖性仅针对单个处理器中执行的指令序列和单个线程中的执行操作。如果两个操作访问同一个变量,且这两个操作其中有一个为写操作,此时这两个操作之间就存在数据依赖性。
数据依赖的三种类型:写后读,写后写,读后写
编译器和处理器在重排序时,会遵守数据依赖性,不会改变存在数据依赖性的两个操作的执行顺序。
as-if-serial语义
不管怎么重排序,单线程程序的执行结果不能被改变
程序顺序规则
java内存模型仅仅要求前一操作对后一操作可见,且前一操作排在第二个操作之前。如果A happens-before B 与 B happens-before A 结果一致。这种情况下java内存模型认为这种重排序并不非法