Java内存模型原理

Java内存模型(JMM)

Java内存模型(Java Memory Model,JMM)是用来屏蔽掉各种硬件和操作系统的内存访问差异,以实现让java程序在各个平台下都能达到一致的内存访问效果。
Java内存模型的主要目标是定义程序中各个变量的访问规则,即在虚拟中将变量存储到内存和从内存中取到变量这样的底层细节。
此处的变量(Variables)与Java编程中的变量有所区别,它包括了实例字段、静态字段和构成数组对象的元素,但不包括局部变量与方法参数,因为后者是线程私有的,不被共享的,自然不会存在竞争问题。

主内存与工作内存

Java内存模型规定了所有的变量都存储在主内存(Main Memory)中。每条线程还有自己的工作内存(Working Memory),线程的工作内存保存着该线程中使用到的变量的主内存副本拷贝,线程对变量的所有操作(读取、赋值等)都必须在工作内存中进行,而不能直接读取主内存中的变量。不同的线程之间也不能直接访问对方工作内存中的变量,线程间的变量传递均需要通过主内存来完成。
在这里插入图片描述

内存交互操作

关于主内存与工作内存之间的具体交互协议,即一个变量如何从主内存拷贝到工作内存,如何从工作内存同步回主内存之类的实现细节,Java内存模型中定义了一下8个操作完成,虚拟机实现时必须保证每一种操作都是原子的,不可再分的(对于double和long类型的变量来说,load、store、read、和write操作在某些平台上允许有例外,后续再讲)。

  • lock(锁定):作用于主内存的变量,它把一个变量标记为一条线程独占的状态
  • unlock(解锁):作用于主内存的变量,它把一个处于锁定状态的变量释放出来,释放后的变量才能被其他线程锁定。
  • read(读取): 作用于主内存的变量,它把一个变量的指从主内存传输到线程的工作内存中,以便随后的load动作使用。
  • load(载入) : 作用于工作内存的变量,它把read操作从主内存中得到的变量值放入工作内存的变量副本中。
  • use(使用): 作用于工作内存的变量,它把工作内存中一个变量的值传递给执行引擎,每当虚拟机遇到一个需要使用到变量的值的字节码指令时将会执行这个操作。
  • assgin(赋值): 作用于工作内存的变量,它把一个从执行引擎接收到的值赋值给工作内存的变量,每当虚拟机遇到一个给变量赋值的字节码指令时会执行这个操作。
  • store(存储): 作用于工作内存的变量,它把工作内存中的值传递给主内存中,方便虽有的write操作。
  • write(写入) : 作用于主内存,它把store操作中从工作内存中得到的变量的值放入主内存的变量中。

如果要把一个变量从主内存复制到工作内存,那么就要顺序的执行read和load操作,如果要把一个变量从工作内存同步到主内存,那么就要顺序执行store和write操作。必须保证顺序执行,无需保证连续执行。除此之外,Java内存模型还规定了在执行上述8中操作时必须满足如下规则:

  • 不允许read和load,store和write操作之一单独出现,即不允许一个变量从主内存中读取但工作内存不接受,或者从工作内存中发起回写了但主内存不接受的情况出现。
  • 不允许一个线程丢弃它的最近的assgin操作,即变量在工作内存中改变了之后必须把该变化同步到主内存中。
  • 不允许一个线程无原因地(没有发生过assgin操作)把数据从线程的工作内存同步回主内存。
  • 一个新的变量只能从主内存中“诞生”,不允许在工作内存中直接使用一个未被初始化(load或assgin)的变量。即就是一个变量实施use、store操作之前,必须先执行过assign和load操作。
  • 一个变量在同一时刻只允许一条线程对其进行lock操作,但lock操作可以被同一线程重复执行多次,多次执行lock后,只有执行相同次的unlock,变量才会被解锁。
  • 如果对一个变量进行lock操作,那么会清空工作内存中此变量的值,在执行引擎使用这个变量前,需重新进行load或assgin操作初始化变量的值。
  • 如果一个变量事先没有被lock操作,那就不允许对它执行unlock操作,也不允许去unlock一个被其他线程锁定的变量。
  • 对一个变量执行unlock操作前,必须先把此变量同步回主内存中(执行store、write操作)。

先行发生原则(happens-before)

先行发生是指Java内存模型中定义的两项操作之间的偏序关系,如果说操作A先行发生与操作B,其中就是说在发生操作B之前,操作A产生的影响能被操作B观察到,影响包括修改了内存中共享变量的值、发送了消息、调用了方法等。

//以下操作在线程A中执行
i = 1;
//以下操作在线程B中执行
j = i;
//以下操作在线程C中执行
i  = 3;

假设线程A中的操作 i=1先行发生与线程B中的操作,可以确定在线程B的操作执行完之后 j的值一定为1,得出这个结论的依据有两个:一是先行发生原则,i=1的结果可以被察觉到;二是线程C还没有登场,线程A结束之后没有其他操作会修改i的值。现在我们再来考虑线程C,我们依旧让线程A跟线程B有先行发生原则,而线程C发生在线程A和线程B之间,但是线程C与线程B没有先行发生原则的关系,那么j的值会是多少呢?,答案是不确定,1和2都有可能,因为线程C对变量i的影响线程B可能会察觉到,也可能不会,这时候线程B就会存在读取到过期数据的风险,不具备多线程安全性。
下面是Java内存模型下一些“天然的”先行发生关系,这些先行发生关系无需任何同步器协助就已经存在,可以在编码中直接使用。

  • 程序次序规则(Program Order Rule):在一个线程内,按照程序代码顺序,书写在前面的操作先行发生于书写后面的操作。准确的来说,应该是控制流顺序而不是代码顺序,因为要考虑分支、循环等结构。
  • 管城锁定原则(Monitor Lock Rule):一个unlock操作先行发生于后面一个对同一个锁的lock操作。这里强调的是同一个锁,而“后面”是指时间上的先后顺序。
  • Volatile变量原则(Voaltaile Variable Rule): 对于一个volatile变量的写操作先行发生于后面对这个变量的读操作,这里的“后面”同样是指时间上的先后顺序。
  • 线程启动规则(Thread Start Rule):Thread对象的start()方法先行发生于次线程的每一个动作。
  • 线程中断规则(Thread Interruption Rule):线程interrupt()方法的调用先行发生于被中断线程代码检测得到中断事件的发生,可以通过Thread.interrupted()方法检测到是否有中断的发生。
  • 线程终止规则(Thread Termination Rule):线程中的所有操作都先行发生于对此线程的终止检测,我们可以通过Thread.join()方法结束、Thread.isAlive()的返回值等手段检测到线程已经终止执行。
  • 对象终结规则(Finalizer Rule):一个对象的初始化完成(构造函数执行结束)先行发生于它的finalize()方法的开始。
  • 传递性(Transitivity):如果操作A先行发生于操作B,操作B先行发生于操作C,那么操作A先行发生于操作C。
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值