竞争
当程序未正确同步时,就会存在数据竞争。java内存模型规范对数据竞争的定义如下:
-
在一个线程中写一个变量,
-
在另一个线程读同一个变量,
-
而且写和读没有通过同步来排序。
当代码中包含数据竞争时,程序的执行往往产生违反直觉的结果(前一章的示例正是如此)。
如果一个多线程程序能正确同步,这个程序将是一个没有数据竞争的程序。
保证
JMM对正确同步的多线程程序的内存一致性做了如下保证:
当程序未正确同步时,就会存在数据竞争。java内存模型规范对数据竞争的定义如下:
在一个线程中写一个变量,
在另一个线程读同一个变量,
而且写和读没有通过同步来排序。
当代码中包含数据竞争时,程序的执行往往产生违反直觉的结果(前一章的示例正是如此)。
如果一个多线程程序能正确同步,这个程序将是一个没有数据竞争的程序。
JMM对正确同步的多线程程序的内存一致性做了如下保证:
在多处理器系统中,处理器通常有一个或多个内存缓存层,通过加速对数据的访问(因为数据离处理器更近)和减少共享内存总线上的流量(因为许多内存操作可以由本地缓存来满足),提高性能。内存缓存可以极大地提高性能,但是它们带来了许多新的挑战。
例如,当两个处理器同时检查相同的内存位置时,会发生什么?在什么条件下它们会看到相同的值?
在处理器级别,内存模型定义了必要和充分的条件,以便知道其他处理器对内存的写操作对当前处理器是可见的,而当前处理器对其他处理器的写操作是可见的。
有些处理器显示了强内存模型,在这种模型中,所有处理器在任何给定的内存位置上看到的值都是完全相同的。
Java内存模型,往往是指Java程序在运行时内存的模型,而Java代码是运行在Java虚拟机之上的,由Java虚拟机通过解释执行(解释器)或编译执行(即时编译器)来完成,故Java内存模型,也就是指Java虚拟机的运行时内存模型。
运行时内存模型,分为线程私有和共享数据区两大类,其中线程私有的数据区包含程序计数器、虚拟机栈、本地方法区,所有线程共享的数据区包含Java堆、方法区,在方法区内有一个常量池。
为了提升性能,JVM 做了 2 件事情。
缓存+重排序
要想解释为什么会出现线程可见性问题,需要从计算机处理器结构谈起。
我们都知道计算机运算任务需要CPU和内存相互配合共同完成,其中CPU负责逻辑计算,内存负责数据存储。
CPU要与内存进行交互,如读取运算数据、存储运算结果等。
由于内存和CPU的计算速度有几个数量级的差距,为了提高CPU的利用率,现代处理器结构都加入了一层读写速度尽可能接近CPU运算速度的高速缓存来作为内存与CPU之间的缓冲:
将运算需要使用的数据复制到缓存中,让CPU运算可以快速进行,计算结束后再将计算结果从缓存同步到主内存中,这样处理器就无须等待缓慢的内存读写了。
内存模型描述给定程序和该程序的执行跟踪,该执行跟踪是否为程序的合法执行。
Java编程语言内存模型通过检查执行跟踪中的每个读取并检查读取所观察到的写入是否根据某些规则有效来工作。
内存模型描述程序的可能行为。一个实现可以自由地生成它喜欢的任何代码,只要所有的结果执行都会产生一个可以由内存模型预测的结果。
这为实现人员执行大量代码转换提供了很大的自由,包括操作的重新排序和删除不必要的同步。
Java编程语言的语义允许编译器和微处理器执行优化,这些优化可以与不正确的同步代码交互,从而产生看似矛盾的行为。这里有一些不正确的同步程序如何显示令人惊讶的行为的例子。