当我们在谈论JMM(Java memory model)的时候,我们在谈论些什么

Posted 天际线_skyline

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了当我们在谈论JMM(Java memory model)的时候,我们在谈论些什么相关的知识,希望对你有一定的参考价值。

  前面几篇中,我们谈论了synchronized、final以及voilate的用法和底层实现,都绕不开一个话题-Java内存模型(java memory model,简称JMM)。Java内存模型是保证线程安全的基础,主要描述了程序中全序的同步动作在不同线程访问共享全局变量时所体现的原子性、可见性和有序性上的限制。

1、定义

  维基百科定义:The Java memory model describes how threads in the Java programming language interact through memory. Together with the description of single-threaded execution of code, the memory model provides the semantics of the Java programming language

  大意是说:Java内存模型描述了多个Java线程如何与内存交互,同单线程执行一样,在多线程场景下内存模型提供了一个合理正确Java编程语意。

  JSR133规范由JSR133专家组开发,并首次在Java5.0中实现。规范详细准确的描述了多线程和内存交互语意,成为Java规范的一部分,改进原有Java语意中错误,模凌两可的部分,保证Java跨平台性。

  JSR133中关于内存模型的定义如下:

  给定一个程序和该程序的一串执行轨迹,内存模型描述了该执行轨迹是否是该程序 的一次合法执行。对于Java,内存模型检查执行轨迹中的每次读操作,然后根据特定规则,检验该读操作观察到的写是否合法。内存模型描述了某个程序的可能行为。JVM实现可以自由地生成想要的代码,只 要该程序所有最终执行产生的结果能通过内存模型进行预测。这为大量的代码转换 提供了充分的自由,包括动作(action)的重排序以及非必要的同步移除。

  内存模型的一个高级、非正式的概述显示其是一组规则,规定了一个线程的写操作何时会对另一个线程可见。通俗地说,读操作r通常能看到任何写操作w写入的值,意味着w不是在r之后发生,且w看起来没有被另一个写操作w‘覆盖掉(从 r 的角度看)。

  在本内存模型规范中使用“读取(read)”这个词时,仅是指读取字段或数组元素的动作(action)。其它操作的语义,如读取数组的长度,执行受检转换以及虚方法 调用,都不会被数据争用直接影响到。JVM 实现有责任确保数据争用不会导致诸如返回错误的数组长度或调用虚方法导致段错误这类不正确的行为。

  内存语义决定着程序中每个时刻能读到的值。每个单个线程中的动作(action)必须表现为被该线程的语义所控制,不包括读操作看到的值由内存模型决定的情况。当指的是这种情景时,我们说该程序遵守线程内(intra-thread)语义。

2、JMM近似模型

  为了方便理解JMM,相比正式模型精确公式化的定义,JSR133提出了一种近似的模型-Happen-Before内存模型,这个描述是正式定义的必要非充分条件。首先需要解释一下同步动作的定义,同步动作指的是锁的加锁解锁,voilate对象读取,线程动作以及探测线程是否结束等。与同步动作对应的是同步边缘(synchronize-with边缘),同步边缘可以理解为同步动作间不可交叠屏障,包括以下几点:

  1. 某个管程m上的解锁动作synchronizes-with(同步与)所有后续在m上的锁定动作 (这里的后续是根据同步顺序定义的)。
  2. 对volatile变量v的写操作synchronizes-with所有后续任意线程对v的读操 作(这里的后续是根据同步顺序定义的)。
  3. 用于启动一个线程的动作synchronizes-with该新启动线程中的第一个动作。
  4. 线程T1的最后一个动作synchronizes-with线程T2中任一用于探测T1是否 终止的动作。T2可能通过调用 T1.isAlive()或者在T1上执行一个join动作来达到这个目的。
  5. 如果线程T1中断了线程T2,T1的中断操作synchronizes-with任意时刻任何其它线程(包括T2)用于确定T2是否被中断的操作。这可以通过抛出一个 InterruptedException或调用Thread.interrupted与Thread.isInterrupted来实现。
  6. 为每个变量写默认值(0,false或null)的动作synchronizes-with每个线程中的第一个动作。
  7. 虽然在对象分配之前就为该对象中的变量写入默认值看起来有些奇怪,从概念上看,程序启动创建对象时都带有默认的初始值。因此,任何对象的默认初始化操作 happens-before程序中的任意其它动作(除了写默认值的操作)。
  8. 调用对象的终结方法时,会隐式的读取该对象的引用。从一个对象的构造器末尾到该引用的读取之间存在一个happens-before边缘。注意,该对象的所有冻结操作happen-before前面那个happens-before边缘的起始点。

  下面我们详细说一下这几条限制,第一条说明了管程(synchronized底层实现monitor)的加锁解锁具有同步关系,包括两点,同一个线程可以对已加锁对管程重复加锁,保证解锁次数一样即可,对于已经被加锁管程,线程阻塞并挂起。第二条volatile变量的写操作会在之后其他线程对volatile变量的读操作有即时的体现,一般通过内存一致性协议实现,一般写操作后会将对象的缓存写入内存,并使得其他核的缓存失效,从而读取内存中最新修改的值,这两条的实现在前面的文章中已有较详细的说明。第三条和第四条都是描述线程的启动在线程的第一个动作之前,线程最后一个动作在线程(探询终止???这个不是很理解)终止之前,不得重排序。第五条描述的是线程A对线程B的中断操作同步与线程B的中断检测动作,两个动作互斥发生。第六条第七条说明了,变量的默认值在线程中第一个动作或者其他操作之前,其实有时候编译器会优化变量默认值但赋值,只要该变量还未使用。第八条说明了对象的字段在构造器中的初始化赋值和对象引用返回,final字段保证初始化赋值在对象引用返回之前,而非final字段不保证。

  所有的同步动作构成的全局顺序,称之为同步顺序,而synchronize-with边缘与程序顺序构成了Happen-before顺序,也就是Happen-before内存模型。Java内存模型是Happen-before内存模型的子集,因为Happen-before模型很多时候违背了因果关系,最为致命的弱点是“值的凭空出现”。关于Java内存模型的正式规范请参考JSR133第7章内容(我也看的云里雾里--!)

3、总结

  总的来说,内存模型这个概念是由Java语言首先提出,在保证java语义完整性健壮性上起到了很大作用,C/C++以及其他语言在锁多线程并发模型上也参考这样的内存模型。理解JMM能让我们更加深刻的正确理解JAVA多线程并发执行中与内存交互的逻辑,从而编写出健壮高效的并发程序。

  

 

以上是关于当我们在谈论JMM(Java memory model)的时候,我们在谈论些什么的主要内容,如果未能解决你的问题,请参考以下文章

死磕 java同步系列之JMM(Java Memory Model)

JMM(Java Memory Model) Java内存模型

java学习:JMM(java memory model)volatilesynchronizedAtomicXXX理解

并发编程之java内存模型(Java Memory Model ,JMM)

Java Memory Model

带你整理面试过程中关于 Java 的内存模型 JMM(Java Memory Model)的相关知识