Java内存模型JMM

Java内存模型

java内存模型(Java Memory Model,JMM)是java虚拟机规范定义的。

0x01.主内存与工作内存

java内存模型规定了所有的变量都存储在住内存。每条线程还有自己的工作内存,线程的工作内存中保存了被该线程使用到的主内存中的变量的拷贝。线程对变量的所有操作都必须在工作内存中进行,而不能直接读写主内存中的变量。不同线程之间也无法直接访问对方工作内存中的变量,线程间变量传递均需要通过主内存来完成。当多个线程操作的变量涉及到同一个主内存区域,将可能导致各自的工作线程数据不一致,这样就导致变量同步回主内存的时候可能冲突导致数据丢失。

这里需要说明一下:

1.主内存、工作内存与java内存区域中的java堆、虚拟机栈、方法区并不是一个层次的内存划分。

2.这里的变量跟我们写java程序中的变量不是完全等同的。这里的变量是指实例字段,静态字段,构成数组对象的元素,但是不包括局部变量和方法参数(因为这是线程私有的)。这里可以简单的认为主内存是java虚拟机内存区域中的堆,局部变量和方法参数是在虚拟机栈中定义的。但是在堆中的变量如果在多线程中都使用,就涉及到了堆和不同虚拟机栈中变量的值的一致性问题了。

0x02.主内存与工作线程交互的基本操作

主内存与工作线程交互的基本操作有以下几种:

  • lock(锁定):作用于主内存的变量,它把一个变量标识为一条线程独占的状态
  • unlock(解锁):作用于主内存的变量,释放锁定状态的变量
  • read(读取):作用于主内存的变量,把一个变量从主内存传输到线程的工作内存中,以便随后的load动作使用
  • load(载入):作用于工作内存的变量,把read操作从主内存中得到的变量值放入工作内存的变量副本中。
  • use(使用):作用于工作内存的变量,把工作内存中一个变量的值传递给执行引擎,每当虚拟机遇到一个需要使用到变量的值的字节码指令时将会执行这个操作
  • assign(赋值):作用于工作内存的变量,把一个从执行引擎收到的值赋给工作内存的变量,每当虚拟机遇到一个给变量赋值的字节码指令时将会执行这个操作
  • store(存储):作用于工作内存的变量,把工作内存的一个变量值传送到主内存,以便随后的write操作使用
  • write(写入):作用于主内存的变量,把store操作从工作内存得到的变量的值放入主内存变量中

虚拟机实现必须保证上面的每一种操作都是原子的。

如果要把一个变量从主内存复制到工作内存,需要顺序执行read和load操作;如果要把变量从工作内存同步回主内存,就要顺序地执行store和write操作。

0x03.交互规则

还规定上述8中基本操作时必须满足如下规则:

1、read和load,store和write必须成对出现
2、不允许一个线程丢弃它的最近的assign操作,变量在工作内存中改变后必须写回主内存
3、不允许一个线程将没有assign过的数据从工作线程同步回主内存
4、一个新的变量只能在主内存中诞生,不允许工作内存中直接使用一个未被初始化(load或assgin)的变量,对一个变量进行use,store之前必须load,assign
5、一个变量同一时刻只允许一条线程对其进行lock,同一个线程对lock操作可重复执行多次,之后需要执行相同次数的unlock变量才会被解锁
6、对变量执行lock操作,将会清空工作内存中此变量的值,在执行引擎使用这个变量前,需要重新执行load或assign操作初始化变量的值。
7、一个变量未被lock不允许unlock,并且不能unlock一个被其他线程锁定住的变量
8、对一个变量执行unlock操作前,必须先把此变量同步回主内存中

0x04.volatile的特殊规则

1)Java内存模型对volatile变量定义了特殊规则:

假定T表示一个线程,V和W分别表示两个volatile型变量,那么进行read,load,use,assign,store,write操作时需要满足如下规则
1、线程T对变量V执行的load和use操作必须成对执行,即执行的前一个动作是load,后一个动作才能是use。后一个动作是use,前一个动作才能是load,线程T对变量V的use动作可以认为是和线程T对变量V的load,read动作相关联,必须连续一起出现(这条规则要求在工作内存中,每次使用V前都必须先从主内存刷新最新的值,用于保证能看见其他线程对变量V所做的修改后的值)

2、与1相对的,线程T对变量V执行的assign和store操作必须成对执行,线程T对变量V的assign动作可以认为是和线程T对变量V的store,write动作相关联,必须连续一个出现(这条规则要求在工作线程中,每次修改V后都必须立刻同步会主内存中,用于保证其他线程可以看到自己对变量V所做的修改)

3、假定动作A是线程T对变量V实施的use或assign动作,假定动作F是和动作A相关联的load或store动作,动作P是和动作F相应的对变量V的read或write动作;类似的,假定动作B是线程T对变量W实施的use或assign动作,假定动作G是和动作B相关联的load或store动作,假定动作Q是和动作G相应的对变量W的read或write动作。如果A先于B,那么P先于Q(这条规则要求volatile修饰的变量不会被指令重排序优化,保证代码的执行顺序与程序的顺序相同)

java内存模型是通过在变量修改后将新值同步回主内存,在变量读取前从主内存刷新变量值这种依赖主内存作为传递媒介的方式来实现可见性。volatile的特殊规则保证了新值能立即同步到主内存,以及每次使用前立即从主内存刷新。

2)volatile对指令重排序的实现:

volatile修饰的变量,赋值后多执行了一个“lock addl $0x0,(%esp)”操作,这个操作相当于一个屏障。
lock的作用是使得本CPU的Cache写入了内存,该写入动作也会引起别的CPU或者别的内核无效化其Cache,这种操作相当于对Cache中的变量做了一次前面介绍Java内存模式中所说的“store和write”操作。所以通过这样一个空操作,可让前面volatile变量的修改对其他CPU立即可见。
lock addl $0x0,(%esp。) 命令把修改同步回内存时,意味着所有之前的操作都已经执行完成,这样便形成了“指令重排序无法越过内存屏障”的效果。

作者

Dench

发布于

2020-08-13

更新于

2020-08-13

许可协议

CC BY-NC-SA 4.0

Your browser is out-of-date!

Update your browser to view this website correctly.&npsb;Update my browser now

×