物理机并发问题在介绍Java内存模型之前,我们先简单了解一下物理机中的并发问题。由于处理器的计算速度和存储设置之间存在几个数量级的差距,现代计算机在内存和存储之间增加了一层读写速度尽可能接近处理器的缓存作为缓冲。processor:将计算中需要用到的数据复制到缓存中,以便快速执行运算。当操作完成后,从缓存中同步回内存,这样处理器就不用等待缓慢的内存读写。基于缓存的存储交互引入了一个新问题:缓存一致性。在多处理器系统中,每个处理器都有自己的高速缓存,它们共享相同的主内存,如图2-1所示。当多个处理器的计算任务涉及到同一个内存区域时,可能会导致各自缓存的数据不一致。此时同步回master时,以谁缓存的数据为准。为了解决一致性问题,就需要每个处理器在访问缓存时都遵循一些协议,读写时都按照协议进行操作。除了增加缓存,为了充分利用处理器内部的计算单元,处理器可能会对输入代码的乱序执行进行优化,处理器会对乱序的结果进行重组计算后顺序执行,保证相同的结果一致,但不保证程序中每条语句的计算顺序与输入代码中的顺序一致。因此,如果有一个计算任务依赖于另一个计算任务的中间结果,那么它的顺序就不能由代码的顺序决定。确保。Java内存模型主存和工作内存Java内存模型规定所有的变量都存储在主存中(这里的主存和物理机的主存同名,可以类比,这里只是一个部分虚拟机内存),这里的变量包括实例字段、静态字段和组成数组对象的元素,但不包括局部变量和方法参数,因为后者是线程私有的。每个线程也有自己的工作内存(可以类比处理器的缓存)。线程的工作内存存储线程使用的变量的主内存副本的副本。)必须在工作内存中,不能直接读写主内存中的变量。不同的线程不能直接访问彼此工作内存中的变量,线程间变量值的传递必须通过主内存来完成。线程、主存和工作内存的交互关系如图2-2内存交互Java内存模型定义了8个操作来完成如何将一个变量从主存复制到工作内存,以及如何从内存同步。工作内存回到主内存的实现细节。虚拟机实现时,必须保证每个操作都是原子的、不可分割的(double和long类变量允许例外)。锁(lock):作用于主存的变量,将变量标识为线程独占状态。解锁(unlock):作用于主存中的变量,解锁一个处于锁定状态的变量,解锁后的变量可以被其他线程锁定。读取(read):作用于主存的变量,将变量的值从主存传送到线程的工作内存,供后续加载动作使用。加载(load):作用于主存的变量,将读操作从主存得到的变量值放入工作存的变量副本中。use(使用):作用于工作内存的变量,将工作内存中一个变量的值传递给执行引擎,每当虚拟机遇到需要使用该变量值的字节码指令时,执行这个操作.Assign(赋值):作用于工作内存变量,将从执行引擎接收到的一个值赋给工作内存中的变量,每当虚拟机遇到字节码指令进行变量赋值时,就执行这个操作。store(存储):作用于工作内存变量,将工作内存中某个变量的值传送到主内存中,以供后续的写操作。写入(write):作用于主存变量,将store操作从工作内存中得到的变量值放入主存的变量中。Java内存模型规定,在执行以上八种基本操作时必须满足以下规则:读取和加载、存储和写入必须成对进行。不允许线程丢弃分配操作。工作内存中的变量发生变化后,必须将变化同步回主内存。内存中没有赋值操作,不允许将工作内存中的变量同步回主内存。新变量只能在主内存中诞生,不允许在工作内存中直接使用一个未初始化的变量(加载或赋值),即对于一个变量实时的赋值和加载操作之前必须进行使用和储存。一个变量只允许一个线程同时加锁,但加锁操作可以多次执行。在解锁变量之前,将执行相同数量的解锁。lock操作会清除工作内存的副本,在执行引擎使用前,需要重新执行load或assign操作来初始化没有lock操作的变量的值,unlock操作是不允许的。不允许解锁另一个线程变量。存储和写入操作必须在解锁操作之前进行,并同步回主存。
