笼统地讲,内存模型主要是针对多处理器环境之上的内存访问顺序、处理器间内存状态修改的可见性做了说明和限制。每个处理器架构都有自己的内存模型,属于硬件级别的内存模型。另外,很多语言(Java ,C++11,Go)在不同硬件内存模型的基础上抽象出了一致的内存模型,属于软件级别的内存模型。总之,内存模型是一个相当抽象的概念,一般来讲,只有在多处理环境做 lock-free 编程的时候才需要考虑到。内存模型可以理解为在特定的操作协议下,对特别的内存或高速缓存进行读写访问的过程抽象。
不同架构的物理机器可以拥有不一样的内存模型,而Java虚拟机也有自己的内存模型。Java虚拟机规范中试图定义一种内存模型,来屏蔽掉各种硬件和操作系统的差异,以实现让让Java程序在各种平台下达到一致的访问效果。
Java内存模型的主要目标是定义程序中各个变量的访问规则,即在虚拟机中将变量存储到内存和从内存中取出变量这样的底层细节。这里的变量不包括局部变量和方法参数,因为他们是线程私有的,不会被共享。
Java 内存模型规定了所 有的变量都储存在主内存中,每条线程有自己的工作内存,线程的工作内存中保存了被该线程使用到的变量的主内存副本拷贝。线程对变量的所有操作都必须在工作内存中进行,而不能直接读写主内存中的变量。不同线程间也无法直接访问对方工作内存中的变量。线程间变量值的传递需要通过主内存完成。
Java内存模型定义了以下8种操作还来完成主内存与工作内存的交互。虚拟机实现时必须保证这些操作都是原子的,不可再分的。
如果要把一个变量从主内存复制到工作内存,就要顺序的执行read和load操作,如果把一个变量从工作内存同步回主内存,就需要顺序地执行store和write操作。他们总是成对出现的,而且是逆序的。Java只是要求这些操作必须按顺序执行,但并没有保证是连续执行。也就是说,read与load之间、store与write之间是可以插入其他指令的,如对主内存a,b两个变量访问,一种可能出现的顺序是read a,read b, loada,load b。除此之外,Java内存模型还规定了在执行上述8种操作必须满足的条件:
不允许 read 和load,store和write操作之一单独出现,即不允许一个变量从主内存读取了但工作内存不接受,或者从工作内存发起了写操作但主内存不接受的情况;
不允许一个线程丢弃assign操作,即工作内存改变了值后必须同步回主内存;
一个新的变量只能在主内存中诞生,不允许在工作内存中直接使用一个未被初始化的变量,就是一个变量在经历use和store之前,必须先经过了assign和load操作;
如果一个变量没有被lock锁定,那么不能对他执行unlock操作,也不允许unlock一个被其他线程锁定的值;
对一个变量进行unlock之前,必须把此变量同步回主内存。
原子性:由Java内存模型直接保证原子性变量操作包括read、load、assgin、use、store和write。这些基本可以保证基本数据类型的操作符合原子性,对于更大范围的操作,提供了lock和unlock的方式。尽管不能直接使用,但提供了字节码指令monitorenter和monitorexit,他们对应的就是synchronized关键字修饰的同步块。
可见性:可见性是指一个线程修改了变量值,其他线程可以立即得知这个修改。Volatile关键字实现了这一点,Java内存模型通过在变量修改后将新值同步回主内存,在变量读取前从主内存中刷新这种依赖内存作为传递媒介的方式来实现可见性。除了volatile关键字之外,synchronized和final关键字也实现了这一效果。同步的可见性源于上面的规则中定义,在执行unlock前,必须把值同步回主内存,final关键字的可见性体现在变量在构造器中构造完成后,其他线程都可以查看到这个值
有序性:在线程内部来看,所有的操作都是有序的,如果在一个线程中观察另一个线程,所有的操作都是无序的。