JMM:Java Memory Model,Java内存模型
JMM为了避免在不同操作系统,不同硬件下内存访问存在差异带来的各种问题,屏蔽了各种硬件以及操作系统的内存,以实现Java程序在各种平台下达到并发一致的效果。
JMM规定所有的变量都存储在主存中(包括实例变量,静态变量),但是不包括局部变量和方法参数。线程持有自己的工作内存,线程从主存中拷贝一份变量到自己的工作内存中,在工作内存中对变量进行操作,然后将变量刷新回主存,而不能直接对主存中的变量进行读写操作。这是JMM定义的线程基本工作方式。
**JMM三大特性:****原子性,**可见性,有序性
原子性
原子性是指该操作执行期间不会被其他线程的操作加塞,即原子性。Java代码块中可以使用synchronized关键字来保证代码块的原子性,即锁。
int i=1; //基本的赋值操作,原子操作
int j=i; // 先读取i的值,再赋值给j,两步操作,不能保证原子性
i++; // 这一步其实可以分为读取i的值,i+1,i+1赋值给i,三步操作,不能保证原子性
i=i+1; // 等价于第三步
可见性
Java提供volatile关键字来保证可见性,线程从主存读取了由volatile修饰的变量,拷贝到自己的工作内存中进行操作,一旦修改,必须将这个改变刷新回主存中,这时候其他线程得知该变量的值已经发生改变,便重新从主存中读取,这就是可见性。
除了volatile之外,final和synchronized也能保证可见性。
有序性
使用volatile来保证有序性,而volatile是通过使用内存屏障来禁止指令重排,这样就保证了有序性。看下面一段代码
int i=1;
int j=2;
volatile int m=3;
int n =4;
int p=5;
在实际的执行代码过程中,并不是顺序来执行的,操作系统为了提高程序的执行效率,在不影响最终结果的情况下,会对指令的执行顺序进行调整,这个过程也叫指令的重排序。相当于i=1,j=2可能并不是按照代码顺序来执行的,有可能是j(p)先赋值,而i(n)后赋值,但是不管i(p),j(n)的赋值顺序如何,i,j的赋值必须在m之前,n,p的赋值必须在m之后。
八种内存交互操作
线程工作内存与主存的操作主要有8个,分别是 lock read load use assign store write unlock。
lock(锁定):锁定主内存中的变量,把变量标识置为线程独有。
read(读取):作用于主内存中的变量,将变量传输到工作内存中。
load(加载):作用于工作内存,将主存的变量读取到工作内存中。
use(使用):作用于工作内存,将变量传输到执行引擎中使用。
assign(赋值):作用于工作内存,从执行引擎中接受值并赋值给变量。
store(存储):作用于工作内存,将工作内存的变量传输到主内存中。
write(写入):作用于主内存,把store操作得到的值写入主内存中。
unlock(解锁):作用于主内存中的变量,释放锁定变量,这样变量才能被其他线程锁定。
其中read,load,store,write不允许单独存在,即read完必须load,store完必须write。
补充一下JMM对8种内存交互操作制定的规则:
- 不允许read、load、store、write操作之一单独出现,也就是read操作后必须load,store操作后必须write。
- 不允许线程丢弃他最近的assign操作,即工作内存中的变量数据改变了之后,必须告知主存。
- 不允许线程将没有assign的数据从工作内存同步到主内存。
- 一个新的变量必须在主内存中诞生,不允许工作内存直接使用一个未被初始化的变量。就是对变量实施use、store操作之前,必须经过load和assign操作。
- 一个变量同一时间只能有一个线程对其进行lock操作。多次lock之后,必须执行相同次数unlock才可以解锁。
- 如果对一个变量进行lock操作,会清空所有工作内存中此变量的值。在执行引擎使用这个变量前,必须重新load或assign操作初始化变量的值。
- 如果一个变量没有被lock,就不能对其进行unlock操作。也不能unlock一个被其他线程锁住的变量。
- 一个线程对一个变量进行unlock操作之前,必须先把此变量同步回主内存。
volatile:不保证原子性,禁止指令重排,可见性
禁止指令重排按顺序又分为编译器指令重排,指令级指令重排,内存指令重排。
valatile禁止指令重排的原理:内存屏障,具体可以了解下Java内存屏障
部分内容参考: