对 Java 内存模型的理解

Java 内存模型

Java内存模型规定了在多线程程序中,什么样的行为是容许出现的,什么样的行为是禁止出现的。这样说可能有点抽象,咱们换一个角度。将程序行为抽象成读操做和写操做,每一个线程有本身的局部变量,同时线程之间还存在共享变量。那么一个多线程程序执行结束后,全部变量会有一个最终值。Java内存模型来决定什么样的值合法,什么样的值不合法。html

内存模型不能要求的太严格,这样会阻碍不少优化方法,下降程序执行的效率,但也不能要求的太松,由于这样会致使一些执行结果违反咱们的直觉。例如指令间的重排序问题,若是线程内部的指令彻底按照程序中指明的次序执行,而且每次执行一条指令,执行的结果当即生效,那么就会阻碍不少优化方法,但这样对程序员是有好处的,由于程序员很容易推断程序的执行结果,这样写出的程序就容易与本身的意图一致。这种内存模型被称为顺序一致性模型(Sequential Consistency)。反之,若是为了优化程序执行效率,重排序的可能性有不少,那么程序的效率是提升了,但对程序员来讲,就很难推断程序的执行结果。这一类的内存模型被称为Relaxed Memory Model。java

这样,咱们就遇到了一个两难的问题:git

  • 内存模型要求严格,那么程序效率低,但程序员容易写对
  • 内存模型要求松,那么程序效率高,但程序员不容易写对

而程序的效率,与程序是否容易写对都很重要。为了解决这个问题,科学家提出了 Data Race Free 的概念,它是对多线程程序同步程度的一种描述,基本的思想是若是多线程程序进行了正确的同步,那么程序员就能够按照顺序一致性模型去推断程序的执行结果,而底层对内存操做的实现,能够按照 Relaxed Memory Model进行优化。程序员

Java 内存模型包含了两方面的内容github

  • 对正确同步的多线程程序,保证其执行结果与在顺序内存模型下执行的结果一致
  • 对没有正确同步要求的多线程程序,进行必定程度的限制,以保证安全性

其中第一方面是与 Data Race Free相关的,第二方面与后面介绍的 Causality Requirements 相关。安全

Data Race Free

Java 内存模型其实定义了好几个概念来讲明什么是正确的同步。markdown

  • 冲突访问(conflicting accesses)

若是存在多个线程,同时访问同一地址,而且至少有一个是写操做,那么这个程序存在冲突访问多线程

  • happen-before order

两个操做之间若是知足下面任意一个条件,就能够说这两个操做之间存在 happen-before order:并发

  1. 同一个线程内,在程序中有前后次序的操做
  2. 构造器的结尾的操做与 finalize 函数的开始的操做
  3. unlock 操做与全部同一把锁上的 lock操做
  4. volatile 变量的读操做与全部对它的写操做
  5. 对变量默认值的写操做与线程启动后的第一个操做
  6. 若是线程 T2 检测到线程 T1 终止执行,那么 T1 的最后一次操做与 T2任意操做
  7. 启动一个线程的操做与此线程内第一个操做
  8. 若是线程 T1 中断了线程 T2,那么此中断操做与其它任何看到 T2 被中断的操做之间。

其中有些我也不是很理解。。oracle

  • data race free

全部存在冲突访问的操做之间都有 happen-before order,那么此多线程程序知足 data race free

  • 正确同步

假如多线程程序在顺序一致性模型下执行,若是它知足 data race free,那么此程序进行了正确的同步。

正确同步的多线程程序,其执行结果与在顺序一致性模型下的执行结果一致。仔细体会下概念之间的关系。有点绕。

另外一方面,若是程序没有正确同步,执行结果也不是任意的,必须对其进行限制,但限制又不能太强,由于太强会阻碍优化。因此 Java 内存模型使用了 Causality Requirements 的概念。

Causality Requirements

为了精肯定义内存模型,Java语言规范中,提出了 Causality Requirements 的概念。不知道是什么缘由,这个概念不多被说起,可是我以为它是很重要的,但同时,也是很是使人费解的。语言规范中,首先定义了 Well-Formed Executions 的概念,如今对内存模型的不少讨论,都是在这一层,它包括了对多线程程序执行中,与锁,volatile变量,执行次序等等相关的规定。若是一个多线程程序的执行知足这些规定,那么这个执行就是 Well-Formed Executions 的。国内有一个系列文章《深刻理解Java内存模型》,主要是在这方面描述Java内存模型。此外,在 Java 并发领域内著名的 Doug Lea 也给出了一个 The JSR-133 Cookbook for Compiler Writers,为编译器做者们提供参考,探讨的也是这方面的问题。可是,内存模型对多线程程序的执行是否合法,不只仅要看它是不是 Well-Formed Executions,此次执行还须要知足 Causality Requirements。

语言规范中规定了一个构造过程,若是经过这个构造过程,能够构造出多线程程序最终的执行结果,那么此次执行就知足 Causality Requirements。构造过程从一个空集合C0开始,每次将其中添加若干操做,若是全部操做都能被添加,那么构形成功。即,

C0 -> C1 -> C2 -> ... -> C

其中 C_i 是 C_(i+1) 的子集。你可能注意到了,以前说的“操做能被添加”,什么叫操做能被添加呢?语言规范中规定了,每个 Ci 都对应一个 Ei,全部 Ei 都要知足 Well-Formed Executions。也就是说,若是你添加了操做后,对应的 Ei 不知足 Well-Formed Executions,那么这个操做就不能被添加。若是最终,你的多线程程序没法构造出这样一个执行链,那么,它的执行结果是非法的。

另外,Java 内存模型最初论文做者维护了一个页面 The Java Memory Model,其中有一个条目叫 Causality Test Cases,给出了一些小例子,以便人们明白哪些行为是知足 Causality Requirements 的,哪些是不知足的。此外,在 Java 并发领域内著名的 Doug Lea 也给出了一个 The JSR-133 Cookbook for Compiler Writers,为编译器做者们提供参考。不过听说这份规范有些地方要求太严格了,开发者们仍是根据Java语言规范和虚拟机规范来开发。


参考资料:Java 语言规范以内存模型

相关文章
相关标签/搜索