JMM(Java Memory Model),JAVA内存模型
1. 定义及其目的
JMM是用来屏蔽掉不同的硬件和操作系统中,内存的访问差异,以实现java程序在不同的平台下达到一致性的访问结果(参考自深入理解java虚拟机)。它的主要目的是定义程序中,各个变量的访问规则,即在虚拟机中将变量存储到内存和内存去除变量这样的底层操作(此处的变量不包括局部变量和方法参数,因为二者是线程私有的)。JMM规定了所有的变量都存储在主内存中,每条线程有自己的工作内存,工作内存中保存着主内存中的变量副本,线程对变量的所有操作,都是操作工作内存中的副本,而不能直接操作内存中的变量。不同的线程不能访问彼此的工作空间,换句话说工作空间是线程私有的。线程、工作内存和主内存关系如图所示:
2. 内存之间的交互操作
关于主内存和工作内存之间交互的协议,JMM定义了一下8种操作来完成,虚拟机实现的时候必须保证下面的每一项操作都是原子的、不可再分的(对于double和long类型的变量来说,load、store、read和write在某些平台上或许有例外,可以拆分成两个32位操作)。
- lock(锁定):作用于主内存的变量,它把一个变量标识为一条线程独占的状态。
- unlock(解锁):作用于主内存的变量,它把一个处于锁定状态的变量释放出来。释放后的变量才可以被其他线程调用。
- read(读取):作用于主内存的变量,它把一个变量的值从主内存传输到线程工作内存中,以便随后的load动作使用。
- load(载入):作用于工作内存的变量,它把read操作从主内存中得到的变量值放入工作内存的变量副本中。
- use(使用):作用于工作内存的变量,它把工作内存中一个变量的值传递给执行引擎,每当虚拟机遇到一个需要使用变量的值得字节码指令时会执行这个操作。
- assign(赋值):作用于工作内存中的变量,它把一个从执行引擎接收到的值赋予工作内存中的变量。
- store(存储):它把工作内存中一个变量的值传送到主内存中,以便随后的write操作使用。
- write(写入):作用于主内存的变量,它把store操作中从工作内存的值写入到主内存的变量中。
JMM要求read和load、store和write必须顺序执行,但是并没有保证连续执行,也就是说两两之间可以插入其他指令。JMM还规定了这8种基本操作时必须满足以下规则:
- 不允许read和load、store和write操作之一单独出现,即不允许一个变量从主内存读取了但工作内存不接受或者相反情况。
- 不允许一个线程丢弃了它最近的assign操作,即变量在工作内存中改变了之后必须把该变化同步回主内存。
- 不允许一个线程无原因的(即没有发生过assign操作)把数据从工作内存同步回主内存。
- 一个新的变量只能在主内存中诞生,不允许在工作内存中世界使用一个未被初始化的变量。
- 一个变量在同一时刻只允许一条线程对其lock操作,但lock可以被同一线程重复操作,多次lock后,必须执行相同数量的unlock操作,变量才能被解锁。
- 如果一个变量执行了lock操作,那将会清空工作内存中此变量的值,在执行引擎使用这个值之前,必须重新执行load火assign操作初始化变量。
- 如果一个变量实现没有执行过lock操作,那就不允许对他执行unlock操作,也不允许去unlock一个被其他线程锁住的变量。
- 对一个变量执行unlock操作之前,必须先把此变量同步回主内存中。
这8种内存访问操作以及上述的规则限定,确定了程序的哪些内存操作在并发下是安全的,换句个人理解的话来说,以上操作和规则限定,保证了程序在运行过程的的原子性,可见性以及有序性,从而保证了在这些操作和规则的保护下的代码是线程安全的。
3. JMM如何具体保证原子性、可见性和有序性问题
在java中提供了一些了和并发相关的关键字,例如volatile,synchronized,final,还有juc包等,这些都是java封装了底层的实现后提供给开发人员使用过的关键字,开发人员可以直接只用synchronized等关键字来控制并发,使得我们并不用关心底层的编译器优化、缓存一致性问题。
原子性保证
在java提供了两个高级字节码指令monitorenter和monitorexit,在java中对应的synchronized来保证代码块内的操作是原子的。
可见性
java中的volatile关键字提供了一个功能,那就是被其修饰的变量在修改后会立即被同步回主内存中,被其修饰的变量每次使用之前也是从主内存总读取。synchronized和final两个关键字也可以实现可见性。
有序性
可以使用synchronized和volatile来保证多线程之间操作的有序性,实现方法有所区别:volatile关键字禁止指令重排,而synchronized关键字保证同一时刻只有一条线程在操作。