JAVA内存模型(JMM)
共享变量:如果一个变量在多个线程的工作内存中都存在副本,那么这个变量就是这几个线程的共享变量。
上面的工作内存其实是java内存模型抽象出来的概念,下面简要介绍一下java内存模型(JMM)。
java内存模型(java memory model): 描述了java程序中各种变量(线程共享变量)的访问规则,以及在JVM中将变量存储到内存和从内存中读取出变量这样的底层细节。
不同的平台,内存模型是不一样的,我们可以把内存模型理解为在特定操作协议下,对特定的内存或高速缓存进行读写访问的过程抽象。Java 虚拟机规范中试图定义一种 Java 内存模型(Java Memory Model,简称 JMM)来屏蔽掉各种硬件和操作系统的内存访问差异,以实现让 Java 程序在各种平台下都能达到一致的内存访问效果,不必因为不同平台上的物理机的内存模型的差异,对各平台定制化开发程序。
更具体一点说,Java 内存模型提出目标在于,定义程序中各个变量的访问规则,即在虚拟机中将变量存储到内存和从内存中取出变量这样的底层细节。
从上图可以得出结论:
所有
的变量都存储在主内存
中每个
线程都有自己独立的工作内存
,里面保存该线程使用到的变量的副本(主内存该变量的一份拷贝)。
JMM关于synchronized的两条规定:
- 线程解锁前,必须把
共享变量的最新值刷新到主内存中
- 线程加锁时,将
清空工作内存中共享变量的值
,从而使用
共享变量时需要从主内存中重新读取最新的值
。(加锁和解锁需要是同一把锁)
线程解锁前对共享变量的修改在下次加锁前对其他线程可见。
JVM主内存与工作内存描述
JVM将内存为主内存
和工作内存
两个部分。
主内存: 主要包括本地方法区
和 堆
- Java 内存模型规定了
所有变量都存储在主内存(Main Memory)中
(此处的主内存与介绍物理硬件的主内存名字一样,两者可以互相类比,但此处仅是虚拟机内存的一部分)。
工作内存: 每个线程
都有一个工作内存,工作内存中主要包括两个部分,一个是属于该线程私有的栈
和 对主存部分变量拷贝的寄存器
(包括程序计数器PC和cup工作的高速缓存区)。
- 每个线程都有自己的
工作内存(Working Memory,又称本地内存.)
,线程的工作内存中保存了该线程使用到的变量,该变量是主内存中的共享变量的副本拷贝
。 - (工作内存是 JMM 的一个抽象概念,并不真实存在。它涵盖了缓存,写缓冲区,寄存器以及其他的硬件和编译器优化。)
线程执行的时候,将首先从主内存读值
,再load到工作内存中的副本
中,然后传给处理器
执行,执行完毕后再给工作内存中的副本赋值
,随后工作内存再把值传回给主存
,主存中的值才更新。
在这个过程中如果出现多个线程同时在处理这些值,岂不是会出现并发问题?
1、所有的变量都存储在主内存中(虚拟机内存的一部分),对于所有线程都是共享的
2、每个线程都有自己的工作内存,工作内存中保存的是主存中某些变量的值的副本拷贝,线程对变量的所有操作都必须在工作内存中进行,不能直接读写主内存中的变量。
3、线程之间无法直接访问对方的工作内存中的变量值的,线程间变量的传递均需要通过主内存来完成。
这种划分与Java运行时内存区域中堆、栈、元空间等的划分是不同层次的划分,两者基本没有关系。硬要联系的话,大致上主内存对应Java堆中对象的实例数据部分、工作内存对应栈的部分区域;从更低层次上说,主内存对应物理硬件内存、工作内存对应寄存器和高速缓存。
JVM内存间交互规则
关于主内存与工作内存之间的具体交互协议,即一个变量如何从主内存拷贝到工作内存、如何从工作内存同步回主内存之类的实现细节,Java 内存模型中定义了下面 8 种操作来完成。
- Lock(锁定):作用于主内存中的变量,把一个变量标识为被一个线程独占的状态。
- Unlock(解锁):作用于主内存中的变量, 将一个变量从锁定状态(Lock)释放出来,释放后的变量才可以被其他线程锁定(Lock)
Read(读取)
:作用于主内存中的变量,将一个变量的值从主内存传输到工作内存中,以便随后的load操作使用Load(加载)
:作用于工作内存中的变量,把read操作从主内存中得到的变量的值放入工作内存的变量副本中。- Use(使用):作用于工作内存中的变量,把工作内存中一个变量的值传递给执行引擎。(每当虚拟机遇到一个需要使用到变量的值的字节码指令时就会执行这个操作。)
- Assign(赋值):作用于工作内存中的变量,把一个从执行引擎接收到的值赋值给工作内存中的变量。(每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作。)
Store(存储)
:作用于工作内存中的变量,把工作内存中的一个变量的值传送到主内存中,以便随后 write 操作使用。Write(写入)
:作用于主内存中的变量,把store操作从工作内存中得到的变量的值放入主内存的变量中。
需知:
- 在将变量从主内存读取到工作内存中,必须顺序执行
read(读取)、load(加载)
; - 要将变量从工作内存同步回主内存中,必须顺序执行
store(存储)、write(写入)
。
这8种操作必须遵循以下规则:
-
不允许
read(读取)和load(加载)、store(存储)和write(写入)
操作之一单独出现
。即不允许一个变量从主内存被读取了,但是工作内存不接受,或者从工作内存回写了但是主内存不接受。不允许一个线程无原因地(没有发生过任何assign操作)把数据从工作内存同步会主内存中
-
不允许一个线程丢弃它最近的一个
assign(赋值)
操作,即变量在工作内存被更改后必须同步改更改回主内存。
即:执行store(存储),write(写入)操作 -
工作内存中的变量在没有执行过
assign(赋值)
操作时,不允许无意义的同步回主内存。 即:执行store(存储),write(写入)操作 -
在执行
use(使用)
前必须已执行load(加载)
,在执行store(存储)
前必须已执行assign(赋值)
。 -
一个变量在
同一时刻
只允许一个线程对其执行lock操作
,一个线程可以对同一个变量重复执行多次lock,多次执行lock后,只有执行相同次数的unlock操作,变量才会被解锁。lock和unlock必须成对出现。 -
一个线程在
lock
一个变量的时候,将会清空
工作内存中的此变量的值,执行引擎在use(使用)
前必须重新read(读取)和load(加载)
初始化变量的值。 -
在执行
unlock
之前,必须首先执行了store(存储)和write(写入)
操作对一个变量执行unlock操作之前,必须先把此变量同步到主内存中(执行store和write操作)
-
线程不允许
unlock
其他线程的lock
操作。并且unlock操作必须是在本线程的lock操作之后。如果一个变量事先没有被lock操作锁定,则不允许对它执行unlock操作;也不允许去unlock一个被其他线程锁定的变量。
从上面可以看出,把变量从主内存复制到工作内存需要顺序执行read、load
,从工作内存同步回主内存则需要顺序执行store、write。总结:
- read、load、use必须成对顺序出现,但不要求连续出现。assign、store、write同之;
- 变量诞生和初始化:变量只能从主内存“诞生”,且须先初始化后才能使用,即在use/store前须先load/assign;
- lock一个变量后会清空工作内存中该变量的值,使用前须先初始化;unlock前须将变量同步回主内存;
- 一个变量同一时刻只能被一线程lock,lock几次就须unlock几次;未被lock的变量不允许被执行unlock,一个线程不能去unlock其他线程lock的变量。
JVM先行发生原则
Java内存模型具备一些先天的“有序性”,即不需要通过任何同步手段(volatile、synchronized等)就能够得到保证的有序性,这个通常也称为happens-before原则。
知识来源: