高并发的理解一

前言

对于java并发程序,我们首先要了解的就是JMM,也就是内存模型,他可以提供内存的可见性保障,针对正确同步的多线程程序而言,因为JMM的存在,使得我们的程序按照我们理解的顺序进行执行(实际并不是),所以,本篇文章的中心点是JMM,并引申出JMM相关的知识点

JMM的概念及作用

Q:java内存模型是什么,他的定义是什么,他做了什么
A:java内存模型就是JMM,他定义了线程与内存之间的一种抽象关系,那么这个抽象关系是什么?首先我们需要知道java执行代码的流程,java并不是一门纯编译的语言,而是以编译为主的语言,如下
1:java代码首先被编译器编译成字节码指令
2:字节码指令被类加载器加载进JVM,然后通过JVM解释执行(热点代码进行编译,此处不深究)
3::在处理器执行被解释执行的指令时则需要与内存进行数据的交互
以上几点则是java执行代码的流程,也是jmm作用于线程与内存之间的地方,但是需要注意的是,代码的执行实际上并不是按照我们编写的顺序在执行,编译器和处理器会进行优化处理,也就是重排序,使得代码执行的顺序是乱序的
jmm通过塑造一个合适的内存模型,提供了内存可见性的保证,如此一来既可以使正确同步后的多线程执行看起来是按照我们理解的方向在执行,也保证了编译器和处理器的优化

Q:jmm如何保证内存可见性
A:编译器和处理器需要对我们的代码进行重排序,那么如果杂乱无章的重排序,则会使得我们的多线程程序执行的结果乱七八糟,所以首先我们需要保证内存的可见性
对于正确同步的多线程而言,如何保证我们的重排序后的代码不会影响到最终的执行结果,首先就是需要保证我们读取变量的正确性,jmm通过如下几点保证了正确同步的多线程即使代码发生重排序(针对的是处理器,总是要重排序的嘛,如下两点实际上是禁止指令重排序,望正确理解此处说的发生重排序),也不会影响到最终的执行结果
1:在编译器进行编译的过程中插入内存屏障,禁止指令的重排序
2:happens-before原则,保证执行顺序不会影响到最终结果,h-b并不一定会禁止重排序

Q:处理器为什么会对指令进行重排序,这样做的目的是什么
A:现代处理器为了避免对内存总线的过度占用,以及为了避免因为读写内存数据带来的延迟,都会选择使用缓存器进行临时数据的保存,从而提升效率
,那么从处理器的角度,通过将内存的数据读写到写缓冲区,在写到内存中,通过这样一个过程,使得对内存的操作顺序发生改变,举个例子:
假设a=b=0
需要执行的操作分别是a=1;x=b
通过写缓存区的处理器操作
1:向写缓冲区写入a=1
2:从内存中读取b
3:将保存的数据刷新到总内存(此时才是将a的值真正写入到内存当中)
代码操作顺序:写入a,读取b
内存的实际操作
1:从内存中读取b
2:将a=1写入内存
内存的操作顺序:读取b,写入a
此处可以发现,处理器因为使用写缓存区,使得与对内存的实际操作与代码逻辑顺序不一致,但是却提升了效率,此处则是发生了重排序

Q:happens-before原则与jmm之间的关系
A:hb原则可以说是jmm的核心了,jmm规定了几种存在hb原则的情况,在单线程或是正确同步的多线程代码中,处理器不会对有可能影响到执行结果的代码顺序进行重排序,如果执行的顺序不会影响到最终的操作结果,那么也是可以进行重排序的

Q:如何理解volatile的重排序规则
A:volatile重排序规则主要包含了以下几点
1:v写之前的操作不能与v写进行重排序
2:v读之后的操作不能与v读进行重排序
3:v写不能与v读进行重排序
对于这些重排序的规则,想要理解他,就需要理解volatile的内存语意
针对v写的内存语意,v写会将缓存中的数据全部刷到主内存当中去
根据v写的内存语意,我们可以了解到,如果前面变量的操作重排序到v写的后面,那么就极有可能造成主内存中的数据与缓存中的数据无法同步,那么此时有操作在去读取数据时,从主内存中拉取的数据可能就不是最新的数据
针对v读的内存语意,jmm会将线程中的本地内存置为无效,并从主内存中拉取数据,那么当v读之后的操作与v读进行了重排序,那么就有可能导致重排序后的操作拉取到的仅仅是缓存的数据,而不是主内存中最新的数据,而v读内存语义则保证了读取到的数据全部都是内存中的数据(缓存的数据会被更新)

猜你喜欢

转载自blog.csdn.net/HNUST_LIZEMING/article/details/88371461