JVM:Java内存模型
Posted 漫步君
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JVM:Java内存模型相关的知识,希望对你有一定的参考价值。
由于计算机的存储设备与处理器之间的运算速度有结果数量级的差距,所以现代计算机系统都不得不加入一层读写速度尽可能接近处理器运算速度的高速缓存cache来作为内存与处理器之间的缓冲,来将运算需要的数据复制到缓存冲,让运算能快速运行。当运算结束后再从缓存同步回内存之中,这样处理器就无需等待缓慢的内存读写了。
除了增加高速缓存之外,为了使得处理器内部的运算单元能尽量被充分利用,处理器可能会对输入代码进行乱序执行优化。处理器会在计算后将乱序执行的结果重组,保证该结果与顺序执行的结果是一致的,但并不能保证程序中各个语句计算的先后顺序与输入代码中的顺序一致。因此如果存在一个计算任务依赖另一个计算任务的中间结果,那么其顺序性将不能靠代码的先后顺序来保证。
Java虚拟机规范中试图定义一种Java内存模型(JavaMemory Model -JMM)来屏蔽掉各种硬件和操作系统的内存访问差异,以实现让Java程序在各种平台下都能达到一致的内存访问效果。在此之前,主流程序语言如C/C++等都是直接使用物理硬件和操作系统的内存模型,因此,会由于不同平台上内存模型的差异,可以导致程序在一套平台上正常,而在另外一套平台上出错。
Java内存模型规定了所有的变量都存储在主内存(Main Memory)中。每条线程还有自己的工作内存(Working Memory),线程的工作内存中保存了被该线程使用到的变量的主内存副本拷贝,对线程变量的所有操作如读取、赋值等都必须在工作内存中进行,而不能直接读写主内存中的变量。不同的线程之间也无法直接访问对方工作内存中的变量,线程间变量值的传递均需要通过主内存来完成。
Java内存模型中定义了8种操作来完成主内存与工作内存之间的具体交互协议,并且虚拟机实现时都保证每一种操作都是原子的、不可分割的。
lock锁定:作用于主内存的变量,他把一个变量标识为一条线程独占的状态。
unlock解锁:作用于主内存的变量,他把一个处于锁定状态的变量释放,释放后变量才可以被其他线程锁定。
read读取:作用于主内存的变量,他把一个变量值从主内存传输到线程的工作内存中,以便load使用。
load载入:作用于工作内存的变量,他把read操作从主内存中得到的变量值放到工作内存的变量副本中。
use使用:作用于工作内存的变量,他把工作内存中一个变量的值传递给执行引擎,每当虚拟机遇到一个需要使用变量值的字节码指令时将会执行这个操作。
assign赋值:作用于工作内存的变量,他把一个从执行引擎接收到的值赋给工作内存的变量,每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作。
store存储:作用于工作内存的变量,他把工作内存中一个变量的值传递到主内存中,以便虽有的write操作使用。
write写入:作用于主内存的变量,他把store操作中从工作内存中得到是变量值存放到主内存的变量中。
如果要把一个变量从主内存复制到工作内存,那就要顺序地执行read和load操作,如果要把变量从工作内存同步回主内存,就要顺序地执行store和write操作。
这8种内存访问操作以及各种规定限制,再加上volatile的一些特殊规定,可以完全确定Java程序中哪些内存访问操作是在并发现完成的,但十分繁琐、实践很麻烦,后面我们会讲到赢等效判断原则——现行发生原则,用来确定一个访问在并发环境下是否安全。
volatile关键字
当一个变量定义为volatile之后,他将具备两种特性:
>第一种是保证此变量对所有线程的可见性,这里的可见性是指当一条线程修改了这个变量的值,新值对于其他线程来说是可以立即得知的。volatile变量在各个线程的工作内存中不存在一致性的问题(在各个线程的工作内存中,volatile变量可能存在不一致的情况,但由于每次使用前都要先刷新,执行引擎看不到不一致的情况,因此可以认为不存在一致性问题),但Java里面的运算并非原子操作,导致volatile变量的运算在并发下一样是不安全的。
由于volatile变量只能保证可见性,在不符合以下两条规则的运算场景中,我们仍然通过加锁(synchronized或者JUC中的原子类)来保证原子性
·运算结果并不依赖变量的当前值,或者能够确保只有单一的线程修改变量的值。
·变量不需要与其他的状态变量共同参与不变约束。
>第二种是禁止指令重排。如果两个线程AB同时执行都需要initialize时,当A中还未被执行,但由于指令重排导致initialize = true被提前执行,此时B中加载配置文件就会出错。
volatile变量读操作的性能消耗与普通变量几乎没有什么差别,但是写操作则可能会慢一些,因为他需要在本地代码中插入许多内存屏障指令保证处理器不发生乱序执行。大多数情况下volatile总开销仍然比锁低,我们在volatile与锁中选择的唯一依据仅仅是volatile的语义能否满足使用场景的需求。
内存模型特性
Java内存模型是围绕着在并发过程中如何处理原子性、可见性和有序性这三个特征来建立的。
>原子性:由于内存模型直接保证的原子变量操作包括read、load、assign、use、store和write,所以基本数据类型的访问读写是具备原子性的(例外是long和double的非原子协定)。如果应用场景需要一个更大范围的原子性保证,Java内存模型还提供了lock和unlock操作来保证满足操作。当然用户可以直接使用synchronized关键字也可以保证具备原子性。
>可见性:当一个线程修改了共享变量的值,其他线程能够理科得知这个修改(volatile、synchronized和final都可以实现可见性)。
>有序性:如果再线程内观察,所有的操作都是有序的(线程内变现为串行的语义);而如果再一个线程中观察另一个线程,所有的操作都是无序的(指令重排序以及工作内存与主内存同步延迟)。Java提供了volatile和synchronized来保证线程之间操作的有序性。
如果Java内存模型中所有的有序性都仅仅靠volatile和synchronized来完成的话,那么有些些操作会变得非常复杂,之所以我们在编写代码的时候无感知是因为上文提到的现行发生原则的存在。
先行发生原则是指:如果操作A先行发生于操作B,那么在发生操作B之前,操作A产生的影响可能被操作B观察到,包括修改了内存中共享变量的值、发送了消息、调用了方法等。
先行发生原则是判断数据是否存在竞争、线程是否安全的主要依据,时间先后顺序与现行发生原则之间没有太大的关系,一切以先行发生原则为准。
随笔,是记忆的一种延伸
以上是关于JVM:Java内存模型的主要内容,如果未能解决你的问题,请参考以下文章