java内存模型概述

java内存模型

    为了控制线程之间的通信,(完成底层封装)

    用来屏蔽掉各种硬件和操作系统之间的内存访问差异,以实现让Java程序在各平台下都能达到一致的内存访问效果

JMM目标:定义程序中各个变量的访问规则,即在虚拟机中将变量存储到内存和从内存中取出变量这样的底层细节(这里变量指代的是实例字段、静态字段和构成数组对象的元素)

    学习JMM可以和内存模型(处理器、高速缓存器、主内存空间)对等起来:内存模型是为了解决处理器和内存之间的速度差异问题,而JMM是为了解决线程之间的信息共享,数据一致性问题

类比:主内存对应硬件系统中的内存部分,工作空间对应高速缓存

java内存模型概述_第1张图片

1、主内存和工作内存

    (1)所有变量均存储在主内存(虚拟机内存的一部分)

    (2)每个线程都对应着一个工作线程,主内存中的变量都会复制一份到每个线程的自己的工作空间,线程对变量的操作都在自己的工作内存中,操作完成后再将变量更新至主内存;

    (3)其他线程再通过主内存来获取更新后的变量信息,即线程之间的交流通过主内存来传递

Note:JMM的空间划分和JVM的内存划分不一样,非要对应的话,关系如下:

    (1)JMM的主内存对应JVM中的堆内存对象实例数据部分

    (2)JMM的工作内存对应JVM中栈中部分区域

2、内存间交互操作

    JMM定义了8种操作(原子操作),虚拟实现时保证这8中操作均为原子操作,以下为8中操作的介绍以及执行顺序:

    (1)lock(锁定):作用于主内存的变量,把一个变量标志为一个线程占有状态(锁定)

    (2)unlock(解锁):作用于主内存的变量,把一个变量从一个线程的锁定状态解除,以便其他线程锁定

    (3)read(读取):作用于主内存的变量,将变量从主内存读取到线程的工作空间,以便后续load操作使用

    (4)load(载入):作用于工作空间的变量,将load操作从主内存得到的变量放入工作内存变量副本中

    (5)use(使用):作用于工作空间的变量,将工作空间中的变量值传递给执行引擎,每当虚拟机遇到一个需要使用到变量的字节码指令时将执行这个操作。

    (6)assign(赋值):作用于工作内存的变量,把一个从执行引擎接收的值赋给工作空间的变量

    (7)store(存储):作用于工作内存的变量,把工作空间的一个变量传到主内存,以便后续write操作使用

    (8)write(写入):作用于主内存的变量,把store操作从工作内存中得到的变量值放入主内存的变量中

 

工作示意图:

java内存模型概述_第2张图片

Note:这些操作之间是存在一些冲突的,需保持顺序执行

有关操作的一些规定:

(1)不允许read和load、store和write操作之一单独出现,即不允许一个变量从主内存读取了但工作内存不接受,或者从工作内存发起回写了但主内存不接受的情况。

(2)不允许一个线程丢弃它的最近assign操作,即变量在工作内存中改变了之后必须把该变化同步回主内存。

(3)不允许一个线程无原因的(没有发生过任何assign操作)把数据从线程的工作内存同步回主内存中。即不能对变量没做任何操作却无原因的同步回主内存

(4)一个新的变量只能在主内存中诞生,不允许在工作内存中直接使用一个未被初始化(load或assign)的变量,就是对一个变量执行use和store之前必须先执行过了load和assign操作

(5)一个变量在同一个时刻只允许一条线程对其进行lock操作,但lock操作可以被同一条线程重复执行多次,多次执行lock后,只有执行相同次数的unlock操作,变量才会被解锁。

(6)如果对一个变量执行lock操作,将会清空工作内存中此变量的值,在执行引擎使用这个变量前,需要重新执行load或assign操作初始化变量的值

(7)如果一个变量事先没有被lock操作锁定,则不允许对它执行unlock操作,也不允许去unlock一个被其他线程锁定住的变量。

(8)对一个变量执行unlock操作之前,必须先把此变量同步回主内存中(执行store write)

Note:以上可以完全确定Java程序中哪些内存访问操作在并发下是安全的。

3、java内存模型对并发提供的保障:原子性、可见性。有序性

(1)原子性:

    Java内存模型直接保证得原子性操作包括read、load、use、assign、store和write这六个。可以认为基本数据类型(long和double除外,64字节在32位上需要两部操作)的变量操作是具有原子性的,而lock和unlock对应在高层次的字节码指令monitorenter和monitorexit来隐式的使用底层的这两个操作,高层次的这两个字节码指令在Java中就是同步块,Synchronized关键字,因此synchronized块之间的操作也具备原子性。

 

(2)可见性

    可见性是指当一个线程修改了共享变量的值,其他线程能够立即得知这个修改。Java内存模型是通过在变量修改后将新值同步回主内存,在变量读取前从主内存刷新变量值这种依赖主内存作为传递媒介的方式来实现可见性的,无论是普通变量还是volatile变量都是如此,普通变量与volatile变量的区别是,volatile的特殊规则保证了新值能立即同步到主内存,以及每次使用前立即从主内存刷新。因此,可以说volatile保证了多线程操作时变量的可见性,而普通变量则不能保证这一点。

    除了volatile外,Synchronized和final也提供了可见性,

Synchronized的可见性是由“对一个变量执行unlock操作之前,必须先把此变量同步回主内存中(执行storewrite操作)”这条规则获得

Final的可见性是指被final修饰的字段在构造器中一旦初始化完成,并且构造器没有把this的引用传递出去(this引用逃逸是一件很危险的事情,其他线程有可能通过这个引用访问到初始化了半的对象),那在其他线程就能看见final字段的值

******这里的this逃逸LZ还是不懂呢!******

(3)有序性

    Java程序在本线程所有操作是有序的(线程表现为串行的),在一个线程中看另一个线程是无序的(指令重排和工作内存与主内存同步延迟现象),可以用Synchronized和volatile来保证线程操作的有序性

Volatile本身就包含禁止指令重排序的语义

Synchronized是因为:一个变量在同一时刻只能被一个线程lock,即串行化操作保证有序

4、先行发生原则

    先行发生原则是java内存模型中定义的两项操作之间的偏序关系,这个原则作为依据来判断是否存在线程安全和竞争问题,如果两个操作之间的关系不在此列,并且无法从下列规则推导出来的话,它们就没有顺序性保障,虚拟机可以对它们随意地进行重排序。以下为8个具体原则:

    (1)程序次序规则(Program Order Rule):在一个线程内,按照程序代码顺序,书写在前面的操作先行发生于书写在后面的操作。准确地说,应该是控制流顺序而不是程序代码顺序,因为要考虑分支、循环等结构。

    (2)传递性(Transitivity):如果操作A先行发生于操作B,操作B先行发生于操作C,那就可以得出操作A先行发生于操作C的结论

    (3)对象终结规则(Finalizer Rule):一个对象的初始化完成(构造函数执行结束)先行发生于它的finalize()方法的开始。

    (4)管程锁定规则(Monitor Lock Rule):一个unlock操作先行发生于后面对同一个锁的lock操作。这里必须强调的是同一个锁,而“后面”是指时间上的先后顺序。

    (5)volatile变量规则(Volatile Variable Rule):对一个volatile变量的写操作先行发生于后面对这个变量的读操作,这里的“后面”同样是指时间上的先后顺序。

    (6)线程启动规则(Thread Start Rule):Thread对象的start()方法先行发生于此线程的每一个动作

    (7)线程终止规则(Thread Termination Rule):线程中的所有操作都先行发生于对此线程的终止检测,我们可以通过Thread.join()方法结束、Thread.isAlive()的返回值等手段检测到线程已经终止执行

    (8)线程中断规则(Thread Interruption Rule):对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生,可以通过Thread.interrupted()方法检测到是否有中断发生。

 

5、volatile型变量

    Volatile是java虚拟机提供的最轻量级的同步机制,它具有可见性和有序性,但不保证原子性,在大多数场景下,volatile总开销仍然比锁要低

volatile是强制从主内存(公共堆)中取得变量的值,而不是从线程的私有堆栈中取得变量的值。如下图所示

java内存模型概述_第3张图片

volatile保证了变量的新值能立即同步到主内存,以及每次使用之前立即从主内存刷新。因此可以说volatile保证了多线程操作时变量的可见性,而普通变量不能保证这一点

(1)volatile可以保证变量对所有线程的可见性,即一条线程修改了变量的值,新值对于其他线程来说是可以立即得知的。volatile变量在各个线程的工作内存中不存在一致性问题(即时存在,由于每次使用之前都得刷新,执行引擎看不到不一致的情况,所以认为是 不存在一致性问题)volatile实际上就使用到了内存屏障技术来保证其变量的修改对其他CPU立即可见。

(2)volatile禁止指令重排,保证有序性

*Volatile不能保证的原子性操作有以下两条:

(1)对变量的写入操作不依赖于该变量的当前值(比如a=0;a=a+1的操作,整个流程为a初始化为0,将a的值在0的基础之上加1,然后赋值给a本身,很明显依赖了当前值),或者确保只有单一线程修改变量。

(2)该变量不会与其他状态变量纳入不变性条件中,(当变量本身是不可变时,volatile能保证安全访问,比如双重判断的单例模式。但一旦其他状态变量参杂进来的时候,并发情况就无法预知,正确性也无法保障)

你可能感兴趣的:(java深究)