背景
为了解决缓存一致性的问题,多个核心在访问缓存时要遵循一些协议,在读写操作时根据协议来操作,这些协议有MSI、MESI、MOSI等,它们定义了何时应该访问缓存中的数据、何时应该让缓存失效、何时应该访问主内存中的数据等基本原则。
为了解决上面提到的多个缓存读写一致性以及乱序排序优化的问题,这就有了内存模型,它定义了共享内存系统中多线程读写操作行为的规范。
Javan内存模型(JMM)
Java内存模型规定了所有的变量都存储在主内存中,这里的主内存跟介绍硬件时所用的名字一样,两者可以类比,但此处仅指虚拟机中内存的一部分。
除了主内存,每条线程还有自己的工作内存。工作内存中保存着该线程使用到的变量的主内存副本的拷贝,线程对变量的操作都必须在工作内存中进行,包括读取和赋值等,而不能直接读写主内存中的变量,不同的线程之间也无法直接访问对方工作内存中的变量,线程间变量值的传递必须通过主内存来完成。
线程、工作内存、主内存关系如图:
内存间的交互操作
Java内存模型定义了以下8种具体的操作来完成:
- lock, 锁定,作用于主内存的变量,它把主内存变量标识为一条线程独占状态。
- unlock,解锁,作用于主内存的变量,它把锁定的变量释放出来。
- read,读取,作用域主内存的变量,它把一个变量从主内存传输到工作内存,以便后续的load操作。
- load,使用,使用于工作内存的变量,它把read操作从主内存得到的变量放到工作内存的变量副本中。
- use,使用,作用于工作内存的变量,它把工作内存的一个变量传递给执行引擎,每当虚拟机遇到一个需要使用变量的值的字节码指令时使用这个操作。
- assign,赋值,作用于工作内存的变量,它把一个从执行引擎接收到的变量赋值给工作内存的变量,每当虚拟机遇到一个给变量赋值的字节码指令时使用这个操作。
- store,存储,作用于工作内存的变量,它把工作内存重一个变量的值传输到主内存中,以便后续的write操作。
- write,写入,作用于主内存,它把store操作从工作内存得到的变量的值放到主内存的变量中。
注意:如果要把一个变量从主内存复制到工作内存,那就要按顺序地执行read和load操作,同样地,如果要把一个变量从工作内存同步回主内存,就要按顺序地执行store和write操作。
另外,Java内存模型还定义了执行上述8种操作的基本规则:
- 不允许read和load、store和write操作之一单独出现。
- 不允许一个线程丢弃它最近的assign操作,即变量在工作内存变化了必须把该变化同步回主内存。
- 不允许一个线程无原因地(即未发生过assign操作)把一个变量从工作内存同步回主内存。
- 一个新的变量必须在主内存中诞生,不允许工作内存中直接使用一个未被初始化(load或assign)过的变量,换句话说就是对一个变量的use和store操作之前必须执行过load和assign操作;
- 一个变量同一时刻只允许一条线程对其进行lock操作,但lock操作可以被同一个线程执行多次,多次执行lock后,只有执行相同次数的unlock操作,变量才能被解锁。
- 如果对一个变量执行lock操作,将会清空工作内存中此变量的值,在执行引擎使用这个变量前,需要重新执行load或assign操作初始化变量的值。
- 如果一个变量没有被lock操作锁定,则不允许对其执行unlock操作,也不允许unlock一个其它线程锁定的变量。
- 对一个变量执行unlock操作之前,必须先把此变量同步回主内存中,即执行store和write操作。
注意,这里的lock和unlock是实现synchronized的基础,Java并没有把lock和unlock操作直接开放给用户使用,但是却提供了两个更高层次的指令来隐式地使用这两个操作,即moniterenter和moniterexit。