Java内存模型定义了程序中各个变量的访问规则,即在虚拟机中将变量存储到内存和从内存中取出这样的底层细节。这里所说的变量包括实例字段、静态字段,但不包括局部变量和方法参数,因为它们是线程私有的,它们不会被共享,自然不存在竞争问题。
为了获得更好的执行效能,Java内存模型并没有限制执行引擎使用处理器的特定寄存器或缓存来和主内存进行交互,也没有限制即时编译器调整代码的执行顺序等这类权利。
Java内存模型规定了所有的变量都存储在主内存中,这里的主内存跟介绍硬件时所用的名字一样,两者可以类比,但此处仅指虚拟机中内存的一部分。
除了主内存,每条线程还有自己的工作内存,此处可与CPU的高速缓存进行类比。工作内存中保存着该线程使用到的变量的主内存副本的拷贝,线程对变量的操作都必须在工作内存中进行,包括读取和赋值等,而不能直接读写主内存中的变量,不同的线程之间也无法直接访问对方工作内存中的变量,线程间变量值的传递必须通过主内存来完成。
线程、工作内存、主内存三者的关系如下图所示:
注意,这里所说的主内存、工作内存跟Java虚拟机内存区域划分中的堆、栈是不同层次的内存划分,如果两者一定要勉强对应起来,主内存主要对应于堆中对象的实例部分,而工作内存主要对应与虚拟机栈中的部分区域。
从更低层次来说,主内存主要对应于硬件内存部分,工作内存主要对应于CPU的高速缓存和寄存器部分,但也不是绝对的,主内存也可能存在于高速缓存和寄存器中,工作内存也可能存在于硬件内存中。
内存间的交互操作关于主内存与工作内存之间具体的交互协议,Java内存模型定义了以下8种具体的操作来完成:
(1)lock,锁定,作用于主内存的变量,它把主内存中的变量标识为一条线程独占状态;
(2)unlock,解锁,作用于主内存的变量,它把锁定的变量释放出来,释放出来的变量才可以被其它线程锁定;
(3)read,读取,作用于主内存的变量,它把一个变量从主内存传输到工作内存中,以便后续的load操作使用;
(4)load,载入,作用于工作内存的变量,它把read操作从主内存得到的变量放入工作内存的变量副本中;
(5)use,使用,作用于工作内存的变量,它把工作内存中的一个变量传递给执行引擎,每当虚拟机遇到一个需要使用到变量的值的字节码指令时将会执行这个操作;
(6)assign,赋值,作用于工作内存的变量,它把一个从执行引擎接收到的变量赋值给工作内存的变量,每当虚拟机遇到一个给变量赋值的字节码指令时使用这个操作;
(7)store,存储,作用于工作内存的变量,它把工作内存中一个变量的值传递到主内存中,以便后续的write操作使用;
(8)write,写入,作用于主内存的变量,它把store操作从工作内存得到的变量的值放入到主内存的变量中;
如果要把一个变量从主内存复制到工作内存,那就要按顺序地执行read和load操作,同样地,如果要把一个变量从工作内存同步回主内存,就要按顺序地执行store和write操作。注意,这里只说明了要按顺序,并没有说一定要连续,也就是说可以在read与load之间、store与write之间插入其它操作。比如,对主内存中的变量a和b的访问,可以按照以下顺序执行:
read a -> read b -> load b -> load a。
另外,Java内存模型还定义了执行上述8种操作的基本规则:
(1)不允许read和load、store和write操作之一单独出现,即不允许出现从主内存读取了而工作内存不接受,或者从工作内存回写了但主内存不接受的情况出现;
(2)不允许一个线程丢弃它最近的assign操作,即变量在工作内存变化了必须把该变化同步回主内存;
(3)不允许一个线程无原因地(即未发生过assign操作)把一个变量从工作内存同步回主内存;
(4)一个新的变量必须在主内存中诞生,不允许工作内存中直接使用一个未被初始化(load或assign)过的变量,换句话说就是对一个变量的use和store操作之前必须执行过load和assign操作;