Java并发编程之JMM

2022-10-28  本文已影响0人  枫叶红花

一、什么是JMM?

  Java虚拟机规范定义了Java内存模型(Java Memory Model,JMM),用于屏蔽掉硬件和各种操作系统访问内存的差异,以期望Java程序在各种平台上都有一致的并发效果,Jvm规范了虚拟机和内存是如何协同工作的,规定了一个线程是如何和何时能够看到另一个线程修改共享变量后的值,以及在必要时是如何同步访问内存中大共享变量。JMM是一种抽象的概念、一组规则,而通过这组规则可以控制各个变量从共享数据区到私有数据区的访问方式。当然谈起JMM,就必然绕不开并发的三大特性:可见性、有序性、原子性,而JMM就是围绕这三大特性展开的。


主存和工作内存示意图

二、并发的三大特性是什么?

并发三大特性是可见性、有序性、原子性
下面将逐一分析每一个特性:
1.可见性
  可见性指的是当一个线程修改了共享变量的值,其它线程要能看到更改后的值。而在JMM中,则是通过修改共享变量的值,然后立即回写到主存中,这样其它线程从主存读取时就能获取最新的数据。
如何保证可见性?
在Java中,可以使用volatile关键字、内存屏障、synchronized关键字、Lock锁、final关键字。

2.有序性
  有序性指的是程序执行的顺序是按照代码执行的顺序执行。在程序执行时,JVM、编译器、处理器都存在指令重排序,会存在有序性问题。
如何保证有序性?
在Java中,可以使用volatile关键字、内存屏障、synchronized关键字、Lock锁。

3.原子性
  原子性指的是一个或多个操作,要么全部执行,并且在执行过程中不受任何因素的中断,要么全部不执行。
如何保证原子性?
在Java中可以使用CAS、synchronized关键字、Lock。

三、JMM和硬件内存架构的关系

  Java内存模型是一种抽象的概念,但是硬件架构却是实实在在的无力层面的东西,而它们之间也存在一定的差异。首先就是硬件内存并没有区分线程栈和堆,而是将所有线程栈和堆都分布在主存中(Main Memory),当然也有部分的线程栈和堆可能会出现在CPU寄存器以及高速缓存中。如下图所示,它们二者之间其实是一个交叉的关系:


JMM和硬件内存示意图

四、Java内存模型(JMM)详解

1.关于主存和工作内存之间的交互

关于主存和工作内存之间的交互,JMM规定了如下八种原子操作:
1.Lock(锁定):作用于主存的变量,将一个变量标识为一条线程独占的状态。
2.read(读取):作用于主存中的变量,将主存中变量的值传送到工作内存中。
3.load(加载):作用于工作内存中的变量,将read操作的值放入工作内存变量副本中。
4.use(使用):作用于工作内存中的变量,将工作内存中变量的值赋值给执行引擎,当虚拟机遇到需要使用变量值的字节指令时,将执行这个操作。
5.assign(赋值):作用于工作内存中的变量,执行引擎将接收到的值传给工作内存中的变量,当虚拟机遇到需要给变量赋值的字节指令时,将执行这个操作。
6.store(存储):作用于工作内存中的变量,将工作内存中变量的值传送到主存中。
7.write(写入):作用于主存中的变量,将store操作的值传送给主存中的变量。
8.unlock(解锁):作用于主存中的变量,将一个锁定状态的变量进行释放,释放后可以給其它线程进行锁定。

JMM规定执行上述8中原子操作时必须满足的条件:

JMM八大原子操作
2.JMM内存可见性保证

Java程序的内存可见性保证可以分为以下三类:

3.volatile写、读的内存语义

对于volatile读:JMM会将工作内存中的变量置为无效,然后从主存中读取最新的值。
对于volatile写:JMM会将工作内存中变量的值立即回写到主存中。

4.volatile的特性

volatile的特性有可见性和有序性。
可见性:对于一个volatile修饰的变量的读,总是能读到这个变量最后写入的值。
有序性:对于一个volatile修饰的变量的读写操作,会在前后加上内存屏障禁止指令的重排序,用来保证有序性。

5.指令的重排序

程序执行的结果和顺序化结果一致,那么指令的执行顺序可以和代码的顺序不一致,此过程就称为指令的重排序。
指令重排序可以根据CPU的特性,适当的对指令进行重排序,以此来提升CPU性能,最大化发挥机器的性能。
相应的,关于volatile重排序的规则,可以看到如下图所示:

volatile禁止重排序
volatile禁止重排序的场景可以总结为如下信息:
6.内存屏障

内存屏障分为JVM层面和硬件层面。
对于Jvm层面的内存屏障,可以分为4种:
LoadLoad屏障:(指令Load1; LoadLoad; Load2),在Load2及后续读取操作要读取的数据被访问前,保证Load1要读取的数据被读取完毕。
LoadStore屏障:(指令Load1; LoadStore; Store2),在Store2及后续写入操作被刷出前,保证Load1要读取的数据被读取完毕。
StoreStore屏障:(指令Store1; StoreStore; Store2),在Store2及后续写入操作执行前,保证Store1的写入操作对其它处理器可见。
StoreLoad屏障:(指令Store1; StoreLoad; Load2),在Load2及后续所有读取操作执行前,保证Store1的写入对所有处理器可见。它的开销是四种屏障中最大的。在大多数处理器的实现中,这个屏障是个万能屏障,兼具其它三种内存屏障的功能

硬件层面的内存屏障:

内存屏障的作用:
1.阻止屏障两边的指令重排序
2.刷新处理器的缓存

五、总结

  JMM其实就是一种抽象的概念、一组规则,通过这组规则控制变量从共享数据区到私有数据区的访问,JMM是围绕三大特性展开的。

上一篇下一篇

猜你喜欢

热点阅读