Jvm笔记总结(十四):Java内存模型

Jvm笔记总结(十四):Java内存模型

PS : 本文乃学习整理参考而来 ,目录参考 [ Jvm系列目录 ]


硬件效率与一致性

        在计算机中绝大多数运算任务都不可能只靠处理器“计算”完成,一般至少要与内存交互,如读取和存储,这部分I/O操作是很难消除的。由于计算机的存储设备处理器的运算速度几个数量级的差距,所以现在计算机会加入一层读写速度尽可能接近处理器运算速度的高速缓存来作为内存与处理器之间的缓存:将运算需要使用到的数据复制到缓存中,让运算能快速进行运算结束后从缓存同步回内存无需处理器等待缓慢的内存读写
        基于高速缓存的存储交互解决了处理器与内存的速度矛盾,但为计算机系统带来了更高的复杂度,因为引入了一个新的问题:缓存一致性在多处理器系统中,每个处理器都有自己的高速缓存,而他们共享一个主内存,当多个处理器的运算任务都涉及同一块主内存区域时,将可能导致各自的缓存数据不一致,那同步回主内存时以谁为准?为了解决一致性问题,需要各个处理器访问缓存时都遵循一些协议,在读写时要根据协议(MSI、MESI..)来进行操作。此处的“内存模型”,可以理解为在特定的操作协议下,对特定的内存或高速缓存进行读写访问的过程抽象。不同架构的物理机器可以拥有不一样的内存模型,Java虚拟机也有自己的内存模型,并且与硬件的访问操作有很高的可比性除了增加高速缓存之外,为了使得处理器内部的运算单元能尽量被充分利用,处理器可能会对输入代码进行乱序执行优化,处理器会在计算之后将乱序执行的结果重组,保证结果与顺序执行是一致的。Java虚拟机的即时编译器也有类似的指令重排序优化。
Jvm笔记总结(十四):Java内存模型


Java内存模型

        Java虚拟机规范中试图定义一种Java内存模型(Java Memory Model,JMM)来屏蔽掉各种硬件操作系统的内存访问差异,以实现让Java程序在各种平台都能达到一致的内存访问效果。而如C、C++等直接使用物理硬件和操作系统的内存模型,会由于不同平台上内存模型的差异,有可能导致程序在一套平台上并发完全正常,而在另外一套平台上并发访问出错,因此在某些场景就必须针对不同的平台来编写程序。

主内存与工作内存:Java内存模型主要目标是定义程序中各个变量的访问规则,即在虚拟机中将变量存储到内存从内存中取出变量这样的底层细节。此处的变量包括了实例字段、静态字段和构成数组对象的元素,但不包括局部变量与方法参数,因为后者是线程私有的,不会被共享,自然不存在竞争问题。

        Java内存模型中规定所有的变量都存储在主内存(Main Memory)中,可理解为堆每条线程有自己的工作内存(Working Memory),可理解为栈。线程的工作内存中保存了被该线程使用到的变量主内存副本拷贝线程对变量的所有操作都必须在工作内存中进行,而不能直接读写主内存中的变量不同线程之间无法直接访问对方工作内存中的变量线程间变量值的传递均需要通过主内存来完成线程、主内存、工作内存三者交互的关系图如

Jvm笔记总结(十四):Java内存模型


内存间的交互操作:关于主内存与工作内存之间具体的交互协议,Java内存模型定义了8种操作来完成。

1.lock(锁定):作用于主内存的变量,把一个变量标识为一条线程独占状态。
2.unlock(解锁):作用于主内存的变量,将处于锁定状态的变量释放解放之后变量才能被其他线程锁定
3.read(读取):作用于主内存的变量,把一个变量的值从主内存传到线程的工作内存,以便随后的load动作使用。
4.load(载入):作用于工作内存的变量,把read操作从主内存中得到的变量值放入工作内存的变量副本中。
5.use(使用):作用于工作内存的变量,把工作内存中一个变量的值传递给执行引擎,每当虚拟机遇到一个需要使用变量字节码指令时将会执行这个操作
6.assign(赋值):作用于工作内存的变量,他把一个从执行引擎接受到的值赋值给工作内存的变量,每当虚拟机遇到 一个给变量赋值的字节码指令时将会执行这个操作
7.store(存储):作用于工作内存的变量,把工作内存中一个变量的值传送到主内存,以便随后的write操作使用。
8.write(写入):作用于主内存的变量,把store操作从工作内存中得到的变量的值放入主内存的变量中。


volatile关键字:
volatile可以说是Java虚拟机提供的最轻量级的同步机制。当一个变量定义为volatile之后,他将具备两种特性:

1.volatile的可见性。
2.volatile禁止指令重排序优化。

其一可见性保证此变量对其他线程来说是可以立即得知的。换言之,对volatile修饰的变量的读写立即体现到主内存中。这就是volatile的可见性。但对于volatile变量的运算在并发下并不是安全的,因为即使volatile的读取是安全的,而运算仍然可能不是原子操作因而不是线程安全的。
其二禁止指令重排序。如果定义的变量没有被使用valatile修饰,就可能会由于指令重排序的优化。而volatile关键字可以避免重排序优化


原子性、可见性与有序性Java内存模型围绕在并发过程中如何如理原子性可见性和有序性这3个特征来建立的。

原子性(Atomicity):由Java内存模型来直接保证的原子性变量操作包括read、load、assign、use、store、write大致可以认为基本数据类型的访问读写是具备原子性的。如果应用场景需要一个更大范围的原子性保证Java内存模型还提供lockunlock操作来满足这种需求,尽管虚拟机未把lock和unlock操作直接开发给用户,但却提供了更高一层次的字节码指令monitorentermonitorexit隐式使用,而这两个字节码指令反应在Java代码中就是同步块——synchronized关键字,因此在synchronized块之间的操作也具备原子性

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

有序性:如果在本线程内观察,所有的操作都是有序的;如果在一个线程中观察另一个线程所有的操作都是无序的。前半句是指“线程内表现为串行的语义”,后半句是指“指令重排序”现象和“工作内存与主内存同步延迟”现象。Java提供了volatilesynchronized两个关键字来保证线程之间操作的有序性