Java基础之内存模型
目录
- Java内存模型简单介绍
- JVM介绍
- 存储方式
- 并发原因
- Java内存模型与系统内存模型
- Java内存模型抽象结构图
- Java并发问题的根源
- Java内存模型-内存间的八种同步操作
- 读取执行步骤
- 写入执行步骤
- 操作规则
- 回顾
Java内存模型简单介绍
- Java内存模型(Java Memory Mode):Java内存模型是Java虚拟机如何与计算机内存(RAM)一起工作的规范.Java虚拟机时整个计算机的模型,所以这个模型自然包含了一个内存模型.也可以说JMM时Java虚拟机内存使用规范.
- 通俗来讲,就时描述Java种各种变量(线程共享变量)的访问规则,以及在JVM种将变量存储到内存和从内存种读取变量这样的底层细节.
- Java内存模型规定了不同线程如何以及何时可以看到其他线程写入共享变量的值以及如何在必要时同步对共享变量的访问.Java Memory Model并不是真实存在的,它时物理内存模型的一个映射.
-
Java 内存模型图文介绍
java内存模型.jpg
JVM介绍
- stack(栈)
- 特点: 存取速度快、对象声明周期确定、数据大小确定.
- 存储数据: 基本类型变量、对象引用
- 位置: 缓存、寄存器、写缓冲区
- heap(堆)
- 特点: 存取速度慢、运行时动态分配大小、对象生命周期不确定、垃圾回收.
- 存储数据: 对象
- 位置: 主内存、缓存.
存储方式
- 一个对象存存储在heap上,对象中的方法方法的成员变量存储在stack上.一个对象的成员变量随着对象本身存储在堆上,无论该对象类型是引用类型或者是基本类型.静态变量和对象类定义存储于堆上.
并发原因
- 存储在堆上的对象可以被持有该对象引用的栈访问.能访问对象,也就能访问该对象中的成员变量.当两个线程同时访问一个对象时,每个线程都拥有该对象成员变量的拷贝.
Java内存模型与系统内存模型
-
关系图如图所示
java内存模型与系统内存模型关系图.jpg - 在系统内存架构中并没有栈(stack)、堆(heap)这种概念,只有寄存器(register)、缓存(cache)、主内存(RAM、Main Memory).理论上说所有的栈和堆都存储在主内存中,但随着CPU运算其数据的副本可能被缓存或寄存器持有.持有的数据尊村CPU-Cache一致性协议.
Java内存模型抽象结构图
java内存模型抽象结构图.jpg- 主内存: 保存了所有的变量
- 共享变量: 如果一个变量被多个线程使用,那么这个变量会在每个线程的工作内存中保有一个副本,这种变量就时共享变量.比如成员变量、静态变量、数组元素等.
- 工作内存: 每个线程都有自己的工作内存,线程独享,保存了线程用到了变量的副本(主内存共享变量的一份拷贝).工作内存负责与线程交互,也负责与主内存交互.为了更高的效率,Java虚拟机、硬件系统可能让工作内有限分配在寄存器、缓存中.
- JMM对共享内存的操作做出了如下两条规定:
- 线程对共享内存的所有操作都必须在自己的工作内存中进行,不能直接从主内存中读写
- 不同线程无法直接访问其他线程工作内存中的变量,因此共享变量的值传递需要通过主内存完成.
Java并发问题的根源
-
假设线程A和线程B同时访问某个对象的成员变量x,当线程A需要操作变量a时会将a副本复制到线程A的工作内存中.
线程A访问变量a逻辑图.jpg -
当线程A未执行完,线程B也要访问变量a
线程B访问变量a逻辑图.jpg -
但是线程A与线程B操作的是自己工作空间中的变量副本.线程A中的副本和线程B中的副本相符且不可见.如果A线程率先完成了任务并写回到主内存,那么线程B的运算就相当于对原数据副本的运算,如果线程B也写回主内存,那么线程A的任务就会丢失.
线程AB变量回写逻辑图.jpg - 为了保证程序的准确性,我们就需要在并发时添加额外的同步操作.
Java内存模型-内存间的八种同步操作
java内存模型-同步操作与规则.jpg-
锁定(lock): 作用于主内存中的变量,将它标记为一个线程独享的变量.
- 通常意义上的上锁,就是一个线程正在使用时,其他线程必须等待该线程任务完成才能继续执行自己的任务.
-
解锁(unlock): 作用于主内存中的变量,解除变量的锁定状态,被解除锁定状态的变量才能被其他线程锁定.
- 执行完成后解开锁
-
读取(read): 作用于主内存的变量,它把一个变量的值从主内存传输到线程的工作内存中,以便随后的load动作使用.
- 从主内存读取到工作内存中.
-
载入(load): 把read操作从主内存中得到的变量值放入工作内存的变量的副本中.
- 给工作内存中的副本赋值
-
使用(use): 把工作内存中的一个变量的值传给执行引擎,每当虚拟机遇到一个使用到变量的指令时都会使用该指令.
- 程序执行过程中读取该值时调用
-
赋值(assign): 作用于工作内存的变量,它把一个从执行引擎接收到的赋值给工作内存的变量,每当遇到一个给变量赋值的字节码指令时执行这个操作.
- 将运算完成后的新值赋回给工作内存中的变量,相当于修改工作内存中的变量
-
存储(store): 作用于工作内存的变量,它把工作内存中的一个变量的值传送到主内存中,以便随后的write操作使用.
- 将该值从变量中取出,写入工作内存中
-
写入(write): 作用于主内存的变量,它把stroe操作从工作内存中得到的变量的值放入主内存的变量中.
- 将工作内存中的值写回主内存
读取执行步骤
读取执行步骤.jpg写入执行步骤
写入执行步骤.jpg操作规则
- 不允许read和load、store和write操作之一单独出现,即不允许一个变量从主内存读取了但工作内存不接受,或者从工作内存发起了,进行回写但主内存不接受的情况出现.
- 不允许一个线程丢弃它的最近的assign操作,即变量在工作内存中 改变了之后必须把该变化同步回主内存.
- 不允许一个线程无原因地(没有发生过任何asssign操作),把数据从线程的工作内存同步回主内存中.
- 一个新的变量只能在主内存中"诞生",不允许在工作内存中直接使用一个未被初始化(load或assign)的变量,换句话说就是对一个变量实施use和store操作之前,必须先执行过了assign和load操作.
- 一个变量在同一个时刻只允许一条线程对其进行lock操作,但lock操作可以被同一条线程重复执行多次,多次lock后,只有执行相同的unlock操作,变量才会被解锁.
- 如果对一个变量执行lock操作,将会清空工作内存中的此变量的值,在执行引擎使用这个变量前,需要重新执行load或assign操作初始化变量的值.
- 如果一个变量实现没有被lock操作锁定,则不允许对它执行unlock操作,也不允许unlock一个被其他 线程锁定住的变量.
回顾
- Java内存模型是一个规范,它规定了不同线程如何以及何时可以看到其他线程写入共享变量的值以及如何在必要时同步对共享变量的访问.
- Java内存模型要求,调用栈和本地变量存储在线程栈上,对象存放在堆上.线程之间的通信必须要经过主内存.
- 定义了同步的八个操作,以及使用这八个操作需要遵守的规则.
网友评论