我们知道 CPU 直接操作高速缓存寄存器,而不是直接操作主内存,那么有缓存的话,就会造成数据一致性问题,所以为了解决这些问题,提出了 JMM 规范。
JMM 全称为 Java Memory Model,Java 虚拟机定义出一种逻辑规范或者说是抽象概念,用来屏蔽各种硬件和操作系统内存的访问差异,实现让 Java 程序在各种平台下都能达到一致的内存访问效果。换句话说就是通过这组规范定义了程序中对共享变量的读写能够互相可见,尤其是多线程情况下的读写操作,都是围绕着多线程的原子性、可见性、有序性展开的。
对JMM 还应该要有认识的点,如下:
不用 Java 虚拟机的内存区域划分,是两种逻辑的存在,是不同层次的划分结果,侧重点各有不同。
主内存可以对应 Java 中的堆内存,工作内存可以对应栈中的部分。
更底层次地来说,主内存对应硬件的物理内存,工作内存对应的是寄存器和高速缓存寄存器。
从图中发现线程操作共享变量,都需把主内存中共享变量拷贝一份到自己的工作内存中,然后才可以进行操作,操作完之后又在写回主内存。如果考虑到高并发场景下,各个线程先把值在自己的工作内存中修改过后,假设有个线程先把值赋值回去了,此时主内存的值已经修改了,但是另一个线程还是拿着自己工作内存中的数据在操作,然后又给写回去了,明显这里第二个线程写回去的数据是有问题的,那么这里我们怎么可以保证,让一个线程修改主内存中的值,然后通知到其他线程,这里就涉及到了线程的可见性问题了,也是 JMM 三大特性之一,在了解线程可见性之前我们先看下 JMM 给我提供的8种原子操作来辅助各线程间内存间的交互?
这八个原子操作类也是实现了线程可见性的基础步骤,如下:
lock
: 锁定,把变量标识为线程独占,作用于主内存变量unlock
:解锁,把锁定的变量释放,其他线程才能使用,作用于主内存read
:读取,把变量值从主内存中读取到工作内存(只是读取到了工作内存,并没有指定到某个变量)load
:载入,把 read 读取到的变量放入工作内存中的副本变量中use
:使用,把工作内存中一个变量的值传递到执行引擎assign
:赋值,把从执行引擎接受到的值赋值到工作内存中的副本变量中store
:存储,把工作内存中的变量传递到主内存中(只是存储到了主内存,并没有指定到某个变量)write
:写入,把 store 存储进来的值写入到主内存中的共享变量下图展示了两个线程同时对主内存中的共享变量 flag 修改过程:
首先线程要从主内存中读取共享变量的值,那么首先就要给这个共享变量加锁(lock
操作),然后通过 read
操作,把 flag 的值从主内存读取到工作内存中(其实就是高速缓冲寄存器),然后再通过 load
操作,将工作内存中 flag 的值加载到变量副本中,然后通过 use
操作,将变量的值传递给 CPU 执行引擎。
CPU 执行完之后,通过 assign
操作,将计算结果存储到变量副本中,接着通过 store
操作,将变量副本的 flag 值从工作内存传递给主内存,然后执行 write
操作,把 flag 的值重写写会到主内存中的共享变量 flag,最后通过 unlock
操作线程释放该锁,其他线程又可以对此变量加锁,重复上述流程。
然后这里有几个内存间交互的规则需要注意下: