JMM

Java Memory Model,即java内存模型,在JSR-133规范中定义

JSR-133

在 1997 年,在此时 Java 版本中的内存模型中发现了几个严重的缺陷,这个缺陷经常会出现诡异的问题,比如字段的值经常会发生改变,并且非常容易削弱编译器的优化能力。

为了修复这些缺陷,JSR-133专家组提出了JSR-133规范。该规范是JSR-176(定义了JavaTM平台 Tiger(5.0)发布版的主要特性)的一部分。该规范的标准内容将合并到JavaTM语言规范、JavaTM虚拟机规范以及java.lang包的类说明中。

JSR-133 的设计目标主要包括

1.保留 Java 现有的安全性保证,比如类型安全,并加强其他安全性保证,比如线程观察到的每个变量的值都必须是某个线程对变量进行修改之后的。
2.程序的同步语义应该尽可能简单和直观。
3.将多线程如何交互的细节交给程序员进行处理。
4.在广泛、流行的硬件架构上设计正确、高性能的 JVM 实现。
5.应提供初始化安全的保证,如果一个对象被正确构造后,那么所有看到对象构造的线程都能够看到构造函数中设置其最终字段的值,而不用进行任何的同步操作。
6.对现有的代码影响要尽可能的小。

JMM 结构规范

JMM规定了所有的变量都存储在主内存(Main Memory)中。每个线程还有自己的工作内存(Working Memory),线程的工作内存中保存了该线程使用到的变量的主内存的副本拷贝,线程对变量的所有操作(读取、赋值等)都必须在工作内存中进行,而不能直接读写主内存中的变量(volatile变量仍然有工作内存的拷贝,但是由于它特殊的操作顺序性规定,所以看起来如同直接在主内存中读写访问一般)。不同的线程之间也无法直接访问对方工作内存中的变量,线程之间值的传递都需要通过主内存来完成。
个人理解:JVM中的虚拟机栈(线程私有)与堆内存(线程共有)则可以看作是对上述规范的一种实现

原子性

JMM保证的原子性变量操作包括read、load、assign、use、store、write,而long、double非原子协定导致的非原子性操作基本可以忽略。如果需要对更大范围的代码实行原子性操作,则需要JMM提供的lock、unlock、synchronized等来保证。

可见性

前面分析volatile语义时已经提到,可见性是指当一个线程修改了共享变量的值,其他线程能够立即得知这个修改。JMM在变量修改后将新值同步回主内存,依赖主内存作为媒介,在变量被线程读取前从内存刷新变量新值,保证变量的可见性。普通变量和volatile变量都是如此,只不过volatile的特殊规则保证了这种可见性是立即得知的,而普通变量并不具备这种严格的可见性。除了volatile外,synchronized和final也能保证可见性。

有序性

JMM的有序性表现为,如果在本线程内观察,所有的操作都是有序的;如果在一个线程中观察另一个线程,所有的操作都是无序的。前半句指“线程内表现为串行的语义”(as-if-serial),后半句指“指令重排序”和普通变量的工作内存与主内存同步延迟的现象。

重排序

在执行程序时为了提高性能,编译器和处理器经常会对指令进行重排序。从硬件架构上来说,指令重排序是指CPU采用了允许将多条指令不按照程序规定的顺序,分开发送给各个相应电路单元处理,而不是指令任意重排。

禁止重排序的意义

重排序对于单线程来说是没有影响的,但在多线程情况下, A线程初始化一个对象,并可能被B线程使用,在允许重排序的情况下,B线程可能会获取到一个未初始化完成的对象,即A线程先赋值对象指针,再进行初始化,此时就会对B线程造成影响

JMM 重排序屏障

LoadLoad Barriers:确保Load1数据的装载先于Load2以及所有后续装载指令
StoreStore Barriers:确保Store1的数据对其他处理器可见(会使缓存行无效,并刷新到内存中)先于Store2及所有后续存储指令的装载
LoadStore Barriers:确保Load1数据装载先于Store2及所有后续存储指令刷新到内存
StoreLoad Barriers:确保Store1数据对其他处理器可见(刷新到内存,并且其他处理器的缓存行无效)先于Load2及所有后续装载指令的装载。该指令会使得该屏障之前的所有内存访问指令完成之后,才能执行该屏障之后的内存访问指令。

先行发生原则 happens-before

前面所述的内存交互操作必须要满足一定的规则,而happens-before就是定义这些规则的一个等效判断原则。happens-before是JMM定义的2个操作之间的偏序关系:如果操作A线性发生于操作B,则A产生的影响能被操作B观察到,“影响”包括修改了内存中共享变量的值、发送了消息、调用了方法等。如果两个操作满足happens-before原则,那么不需要进行同步操作,JVM能够保证操作具有顺序性,此时不能够随意的重排序。否则,无法保证顺序性,就能进行指令的重排序。

happens-before

1.程序次序规则(Program Order Rule):在同一个线程中,按照程序代码顺序,书写在前面的操作先行发生于书写在后面的操纵。准确的说是程序的控制流顺序,考虑分支和循环等。
管理锁定规则(Monitor Lock Rule):一个unlock操作先行发生于后面(时间上的顺序)对同一个锁的lock操作。
2.volatile变量规则(Volatile Variable Rule):对一个volatile变量的写操作先行发生于后面(时间上的顺序)对该变量的读操作。
3.线程启动规则(Thread Start Rule):Thread对象的start()方法先行发生于此线程的每一个动作。
4.线程终止规则(Thread Termination Rule):线程的所有操作都先行发生于对此线程的终止检测,可以通过Thread.join()方法结束、Thread.isAlive()的返回值等手段检测到线程已经终止执行。
5.线程中断规则(Thread Interruption Rule):对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断时事件的发生。Thread.interrupted()可以检测是否有中断发生。
6.对象终结规则(Finilizer Rule):一个对象的初始化完成(构造函数执行结束)先行发生于它的finalize()的开始。
7.传递性(Transitivity):如果操作A 先行发生于操作B,操作B 先行发生于操作C,那么可以得出A 先行发生于操作C。
注意:不同操作时间先后顺序与先行发生原则之间没有关系,二者不能相互推断,衡量并发安全问题不能受到时间顺序的干扰,一切都要以happens-before原则为准

参考资料:
https://blog.csdn.net/u011080...

https://blog.csdn.net/zjcjava...

https://blog.csdn.net/zjcjava...

https://www.cnblogs.com/cxuan...


老污的猫
30 声望5 粉丝