您现在的位置:首页行业资讯

Java内存模型的核心问题就是如何解决一致性问题--中享思途

不知道你有没有听过Java内存模型(Java Memory Model)?如果没听过,那Java多线程编程你肯定不陌生吧。你去随便找一本讲Java多线程编程的书籍,你就会发现,基本上都会讲到Java内存模型。到底什么是Java内存模型呢?它跟多线程又有什么关系呢?
要了解这块知识,最权威的材料莫过于JSR-133(JMM规范)了。不要被JSR-133这个奇怪的单词吓到了。实际上看懂JSR-133并不难,我也是几乎不费力气的就看懂了。但是,尽管JSR-133每个章节每个概念都不难看懂,但是,要真正理解JMM还是不容易的,不信?那你可以拿下面这几个问题自测一下,看是否都能会答上来呢?
1)为什么叫Java“内存”模型?JSR-133讲的内容跟”内存“有什么关系?
2)为什么Java内存模型总是跟多线程放到一块讲?
3)处理器可见性和重排序跟Java内存模型有什么关系?
4)JMM为什么要讲volatile, synchronized, final?
透彻的无盲点的理解JMM是需要非常广和深的计算机理论知识的,涉及到计算机体系结构,操作系统,编译原理,JVM虚拟机规范等,我能力有限也不想假装大牛,所以为了能顺畅的阐述JMM的来龙去脉,对于有些概念进行了假设,猜想,默认,细节简化,做到“不求甚解”只是为了能很短的篇幅内完整的阐述清楚JMM的来龙去脉,并且能让你看懂。不过,尽管有些细节的忽略,但并不影响文章的正确性。
JMM解决的核心问题是什么?
我先概括性的给出四个结论,如果你看不懂,也没关系,我接下来会针对每一点展开分析。
1)多线程情况下,一致性问题是如何产生的?
硬件内存模型导致的,其中包括CPU缓存和指令重排序。
2)多线程环境下,有哪些一致性问题?
CPU缓存导致的:缓存与内存数据的一致性问题;
指令重排序导致的:指令真正被执行顺序与程序书写的顺序的一致性问题;
3)上述一致性问题带来哪些编程上的问题?
多线程下并发的执行存在race condition的代码,上述的不一致性问题会导致代码执行结果的不确定性(随机性)。
4)Java如何来解决上述所讲的一致性问题?
通过一些方法来禁止CPU缓存和禁止指令重排序
什么是硬件内存模型?
不同体系结构的内存模型是可能有差别的,我们拿其中一种常见的并且简化一下来讲解。处理器为了保证高效的运行,提高CPU指令执行的效率,引入了两个优化:CPU缓存和指令重排序。
1)CPU缓存:导致数据的不一致性
CPU引入缓存的目的主要是解决内存读写速度和CPU处理速度的差别,使CPU对数据的读写不需要等待较慢的内存。
在只有一个CPU的情况下,使用缓存是没有任何问题的。但在多CPU的情况下,每个CPU都会有一个独立的缓存,但共享内存。这就有问题了。因为每次CPU写操作,都会先将数据写到缓存,然后再择机刷到内存。同样,CPU读操作,也是先从缓存中读取,不命中再从内存捞。
我们知道缓存会有数据一致性问题(不懂自己查查),CPU缓存也不例外,一个CPU写的数据可能不会被另个CPU立刻读到,这就是我们经常提到的可见性问题。
2)指令重排序:导致指令执行顺序的不一致性
对于一组指令序列,比如A,B,C,D,E...(每个英文代表一条指令),指令的顺序有可能并非是最终执行的顺序,CPU为了提高执行效率(比如,提高缓存的命中率),可能会在执行的时候,只要保证存在数据依赖的两条指令,执行先后顺序不变(即偏序有序),就可以肆无忌惮的对指令序列重排序。
对于单CPU的情况下,这样的指令重排序并没有什么问题,CPU承诺最终执行的结果跟没有重排序的执行结果是一样子的,不然它也不敢这么肆无忌惮的瞎搞。但是对于多CPU的情况下,CPU就不做任何承诺了,一个CPU看到的数据更新操作可能跟其预期的(未指令重排序执行的)是不一致的了。
不过,上述两种CPU优化策略(缓存和指令重排序)大部分情况下都不会出问题,只有当两个CPU并行执行存在数据操作冲突的指令(其中有一个写操作)的时候,才会存在问题。不过,CPU是否听之任之问题的存在呢?当然不是。
有些处理器直接就在硬件层面上禁止重排序和缓存,所以就不存在上面说的问题。但大部分CPU并不想为此牺牲效率,所以默认将问题丢给了上层代码,但提供了一些基本的指令来支持禁止缓存和指令重排。
JMM与硬件内存模型有什么关系?
在CPU看来,任何高级语言编写的代码最终也都是要编译成一组操作数据的指令序列,CPU最终执行的就是这样一组指令序列。因为并不是所有的处理器都会妥妥的解决好缓存和指令重排带来的问题,像JAVA这样的跨平台语言,就需要在语言层面解决编译之后的指令的在不同处理器上可能存在的缓存和指令重排序问题。
那如何来解决呢?所以JMM规范就顺理成章的诞生了。那又为什么叫Java内存模型呢?那是因为JMM解决的是我们刚刚讲到的硬件内存模型存在的问题,所以起名叫做Java内存模型。
JMM跟多线程的又有什么关系?
为什么每次提到JMM都要跟多线程联系在一起呢?因为单线程下,程序被分派给一个CPU执行。而硬件内存模型里面的缓存和指令重排序的问题也只是在多个CPU并行执行并存在冲突的数据操作的时候才会有问题。
也就是说在Java不涉及多线程运行代码的情况下,也就不存在多CPU并行执行代码,也就不存在上面说的种种问题。所以Java内存模型讲的内容的前提都是在多线程并发执行race condition代码的时候产生的。
硬件内存模型带来哪些编程问题?
我们举两个例子说明一下,硬件内存模型中缓存和指令重排序对Java多线程编程的影响。
首先来看下下面的代码:
public class Server {
private boolean stoped = false;
void run() {
// ....
While (!stoped) {
// ....
}
// ....
}
void stop() {
stoped = true;
}
}
一个线程执行run(),当需要停止Server的时候,另一个线程执行stop(),将stoped设置成true。
从代码角度看,貌似没有什么问题,但是将代码翻译成机器指令之后,就会发现stoped值会因为CPU缓存,存在一致性问题。
当一个CPU执行stoped=true指令时 ,会先写入CPU缓存,具体刷新到内存的时间不确定,另一个执行run()指令序列的CPU,有可能之前已经缓存了stoped值在CPU缓存中,另一个CPU即便将stoped设置为了true,此CPU可能也迟迟读不到最新值。
从Java代码的层面来看,就会出现一种奇怪的现象,明明stoped已经设置为true了,server仍然一直运行。
我们再来看下面这段代码:
public class ReorderExample {
int val = 0;
boolean flag = false;
public void fun1() {
val = 1; // A
flag = true; // B
}
public void fun2() {
if (flag) { // C
int ret = val + val; // D
// ...
}
}
}
看上面这段代码,我们预期的ret值应该是2,因为只有flag=true的时候才会执行ret=val+val,而flag设置为true之前val会被设置为1。但实际上,在多线程并发执行fun1和fun2的情况下,会出现ret=0的情况,这就是操作重排序导致的结果,我们来分析一下。
在fun1()中,A, B操作没有依赖关系,如果发生指令重排序,B先于A执行,flag会先被true,然后val还没有被设置为1,这个时候C通过,D计算结果便成了0。
这样子,在多线程下,代码的执行结果就不确定了。
JMM如何解决多线程面临的问题?
问题很明显是因为硬件的内存模型导致的,我们要解决这个问题,可以有几个途径:
1)硬件层面解决
处理器层面上就禁止缓存和指令重排序,对于Java这种跨平台的语言来说,显然是不行的。
2)JVM默认实现
上面有问题的代码,在编译成指令时就事先通过处理器提供的 指令,将其编译成一组多CPU下执行安全的指令序列。这个显然是不合适的,因为我们事先并不知道程序是否是在多线程下执行,过度的严格的限制会导致代码执行的效率。
3)程序员控制
通过提供一些语法关键词,有程序员发现负责代码在多线程并发竞态下的问题,通过这些关键词来控制,做到多线程下执行结果并非不确定。
JMM讲到了三个关键词,volatile, synchronized, final,其中:
a) final本身是指常量类型,但在构造函数中使用final会重排序问题,所以重新定义了final对重排序的规则语义。
b) synchronized本身的虽然并未对解决重排序问题,但通过互斥访问共享变量,做到临界区代码串行执行,并且synchronized语义本身定义了每次进入和退出都要同步CPU缓存,所以CPU缓存的数据一致性问题不存在,并且指令重排序也并不影响指令执行的确定性。
c) volatile这个关键词就是纯粹为了解决可见性,重排序等问题产生的。volatile修饰的数据的读写读写操作会让CPU缓存主动跟主存同步,来保证一致性,另外通过禁止某些指令重排序,可以完美解决指令重排序导致的问题。

【关键词:正规Java开发培训,Java工程师培训班,思途学Java多少钱,中享思途】

 st_bottom
青岛Java培训,青岛HTML5培训,青岛UI培训,青岛web开发培训,青岛IT培训,java培训,ui培训,HTML5培训,java就业培训,专业ui设计,web开发培训,IT培训,思途教育,青岛思途,中享思途
Copyright © 青岛思途共享科技信息服务有限公司 鲁ICP备14027489号-2

鲁公网安备 37021402000988号

青岛Java培训,青岛HTML5培训,青岛UI培训,青岛web开发培训,青岛IT培训,java培训,ui培训,HTML5培训,java就业培训,专业ui设计,web开发培训,IT培训,思途教育,青岛思途,中享思途