-
深入理解 Java 内存模型(一)- 内存模型介绍
-
深入理解 Java 内存模型(二)- happens-before 规则
-
深入理解 Java 内存模型(三)- volatile 语义
-
深入理解 Java 内存模型(四)- final 语义
-
深入理解 Java 内存模型(五)- 锁
要解决的问题
在硬件环境下,基于高速缓存的存储交互很好的解决了处理器与内存的速度之间的矛盾,但同时也带给计算机系统带来了复杂的缓存一致性问题。简单点说,在多处理器系统中,每个处理器都有自己的高速缓存,他们共享主内存,当在某个时刻,多个处理器的运算任务都涉及同一块内存区域,很有可能各自缓存中的运算结果不一致,此时若要执行将高速缓存中的数据同步回主内存的操作,那么问题就来了:系统以哪个缓存的数据为准? 由此便出现了解决缓存一致性的协议、规范。“内存模型”一词即指在特定的操作协议下,对特定的内存和高速缓存进行读写访问的过程抽象,不同架构的物理机器可以拥有自己的内存模型。JVM 也实现了这样的一套规范模型。
并发编程模型的分类
在并发编程中,需要解决的两个关键问题分别是:线程之间如何通信(以何种机制来交换信息)以及线程之间如何同步(不同线程之间操作发生的相对顺序机制)。
线程之间的通信机制有两种:共享内存和消息传递。在共享内存的并发模型里,线程之间共享程序的的公共状态,线程之间通过读-写内存中的公共状态来隐式进行通信;在消息传递的并发模型里,线程之间没有公共状态,必须通过明确的发送消息来显示通信。
在共享内存的并发编程模型里,同步就显得至关重要,必须显示指定。Java 的并发采用的就是共享内存模型,不同的线程之间无法访问对方本地内存中的变量,线程间变量值的传递均需要通过主内存来完成。
Java 内存模型的抽象
在 Java 中,所有实例域、静态域和数组元素存储在堆(主内存主要对应了堆中对象实例数据部分)中,堆内存在线程之间共享,因此他们也叫共享变量。而局部变量、方法参数和异常处理器参数是属于线程私有的,不会被共享。每个线程还有自己的工作内存,其中保存了被该线程使用到的变量的主内存副本拷贝,线程对共享变量的所有操作(读取、赋值等)都必须在工作内存中进行,而不能直接读-写主内存中的共享变量。是不是和如上描述的硬件机制非常相似!实际上就是如此:主内存可以看出直接对应于物理硬件的内存,工作内存优先使用的是寄存器和高速缓存,因为程序运行时主要访问的是工作内存。
如上图所示,线程A和线程B通过共享变量实现了隐式通信,同时通过 JMM 控制实现多个线程之间的同步。解决了多核、多线程间数据的共享以及对内存操作的有序性的问题。
内存间交互操作细节
关于主内存与工作内存之间具体的交互协议,即一个变量如何从主内存拷贝到工作内存、又如何从工作内存同步回主内存的实现细节,JMM 中定义了 8 种操作来完成,且在虚拟机实现中都保证以下的每种操作都是原子的、不可再分的。
lock(锁定):作用于主内存的共享变量,把一个共享变量标识为一条线程独占的状态
read(读取):作用于主内存的共享变量,把一个共享变量的值从主内存传输到线程的工作内存的读缓冲区中,以便随后的 load 操作使用
load(载入):作用于工作内存的共享变量,把 read 操作从主内存中读取到读缓冲区中的变量值放入工作内存的共享变量副本中
use(使用):作于于工作内存的共享变量副本,把工作内存中的共享变量副本的值传递给执行引擎
assign(赋值):作用于工作内存的共享变量副本,把一个从执行引擎接收到的值赋给工作内存中的共享变量副本
store(存储):作用于工作内存的共享变量副本,把工作内存中的共享变量副本的值通过写缓冲区传送到主内存中,以便随后的 write 操作使用
write(写入):作用于主内存的共享变量,把 store 操作存储到写缓冲区的值放入主内存的共享变量中
unlock(解锁):作用于主内存的共享变量,把一个处于锁定状态的共享变量释放出来,释放后的变量才可以被其它线程锁定
上述 8 种原子操作还必须满足如下规则:
不允许 read 和 load、store 和 write 操作之一单独出现,即不允许一个变量从主内存读取了但工作内存不接受,或者从工作内存发起了回写但主内存不接受的情况出现。
read、load 和 store、write 此两类操作必须按顺序执行,但并非必须连续执行,也即在 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 操作)