Java 内存模型(Java Memory Model)
读《深入理解Java虚拟机》第三版,周志明著,笔记。
了解Java内存模型是开始Java并发编程的基础。
官方文档 https://download.oracle.com/otndocs/jcp/memory_model-1.0-pfd-spec-oth-JSpec/
《Java虚拟机规范》Java SE 6 https://docs.oracle.com/javase/specs/jvms/se6/html/Threads.doc.html
在后续的jsr-133中定义有所差异,这里主要以书和虚拟机规范se6的说明主,底层没有变化,后者更加详细。
一、基本概念
Java 内存模型(Java Memory Model,JMM)试图屏蔽各种硬件和操作系统的内存访问差异,以实现让 Java 程序在各种平台下都能达到一直的内存访问效果。
定义 Java 内存模型,并不是一件容易的事情:需要足够严谨,让 Java 的并发内存访问操作不会产生歧义;同时,需要足够宽松,使得虚拟机的实现能有足够的自由空间去理用硬件的各种特性。
Java 内存模型的主要目的是定义程序中各种变量的访问规则,即关注在虚拟机中把变量值储存到内存和从内存中取出变量值这样的底层细节。
-
主内存
(Main Memory) -
工作内存
(Working Memory)
所有变量都存储在主内存。
每条线程都有自己的工作内存,保存变量的主内存副本,线程不能直接操作主内存的数据。
线程之间不能直接访问对方的工作内存的变量。
如图:
Java 内存模型与Java内存区域中的 Java堆、栈、方法区等,是在不同层次对内存的划分,这两者基本上是没有任何关系的。如果是一定要勉强关联。
- 主内存:对应Java堆中的对象实例数据部分。
- 工作内存:对应虚拟机栈中的部分区域。
从更基础的层次上说:
- 主内存:直接对应于物理硬件的内存。
- 工作内存:虚拟机可能会优先将内存储存于寄存器和高速缓存中。
如图:
二、内存间交互操作
JSR-133 已经定义为 四种操作,但是我还是在这里记录8种操作,原理不变,8种更加详细。
-
lock
(锁定):作用于主内存的变量,它把一个变量标识为一条线程独占的状态。 -
unlock
(解锁):作用于主内存的变量,它把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定。 -
read
(读取):作用于主内存的变量,它把一个变量的值从主内存传输到线程的工作内存种,以便随后的 load 动作使用。 -
load
(载入):作用于工作内存的变量,它把 read 操作从主内存种得到的变量值放入工作内存的变量副本中。 -
use
(使用):作用于工作内存的变量,它把工作内存中一个变量的值传递给执行引擎,每当虚拟机遇到一个需要使用变量的值的字节码指令时将会执行这个操作。 -
assign
(赋值):作用于工作内存的变量,它把一个从执行引擎接收的值赋给工作内存的变量,每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作。 -
store
(存储):作用于工作内存的变量,它把工作内存中一个变量的值传送到主内存中,以便随后的 write 操作使用。 -
write
(写入):作用于主内存的变量,它把 store 操作从工作内存中得到的变量的值放入主内存的变量中。
read 和 load 操作,store 和 write 操作,是顺序执行,但是不要求连续,所以中间可能会有其他指令。
JSR-133的定义:
- write The variable written to and the value written.
- read The variable read and the write seen (from this, we candetermine the value seen).
- lock The monitor which is locked.
- unlock The monitor which is unlocked.
个人理解演示图:
执行以上 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 操作)。