对 Java 内存模型的理解

Java 内存模型

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

内存模型不能要求的太严格,这样会阻碍很多优化方法,降低程序执行的效率,但也不能要求的太松,因为这样会导致一些执行结果违反我们的直觉。例如指令间的重排序问题,如果线程内部的指令完全按照程序中指明的次序执行,并且每次执行一条指令,执行的结果立即生效,那么就会阻碍很多优化方法,但这样对程序员是有好处的,因为程序员很容易推断程序的执行结果,这样写出的程序就容易与自己的意图一致。这种内存模型被称为顺序一致性模型(Sequential Consistency)。反之,如果为了优化程序执行效率,重排序的可能性有很多,那么程序的效率是提高了,但对程序员来说,就很难推断程序的执行结果。这一类的内存模型被称为Relaxed
Memory Model。

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

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

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

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

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

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

Data
Race Free

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

  • 冲突访问(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 被中断的操作之间。

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

  • 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 语言规范之内存模型

对 Java 内存模型的理解,布布扣,bubuko.com

时间: 2024-10-07 03:09:38

对 Java 内存模型的理解的相关文章

全面理解Java内存模型(JMM)及volatile关键字(转)

原文地址:全面理解Java内存模型(JMM)及volatile关键字 关联文章: 深入理解Java类型信息(Class对象)与反射机制 深入理解Java枚举类型(enum) 深入理解Java注解类型(@Annotation) 深入理解Java类加载器(ClassLoader) 深入理解Java并发之synchronized实现原理 Java并发编程-无锁CAS与Unsafe类及其并发包Atomic 深入理解Java内存模型(JMM)及volatile关键字 剖析基于并发AQS的重入锁(Reetr

深入java内存模型(一)

最近本来想深入学习一下java线程,很想知道其中实现的原理,比如线程资源的共享,线程私有空间,以及线程直接的同步控制等.如果能了解它的实现,对于深入学习线程,会有很大的帮助.最近正在看一份<深入java内存模型>的资料.讲的就是java线程方面的实现原理,拿出来分享一下. 说到线程,我们首先想到的是线程的通信.学习操作系统时,线程通信有两种,一种是通过共享内存,另一种是通过消息传递.共享内存属于隐式的通信,线程之间共享一块内存,线程都可以往这块内存写数据,读数据就实现了通信.而消息传递时一个显

从5个方面让你真正了解Java内存模型

前言 首先我们在了解java内存模型之前先看一下计算机内存模型,理解了计算机内存模型的话后面在看JMM就会简单的多. 计算机内存 计算机是由CPU.主存.磁盘等组成的(简单引出问题熬)我们都知道计算机执行程序的指令都是由CPU来执行的,执行的时候是要处理数据的,这些数据通常存储在主存中. 如图所示,这时候问题来了,CPU的执行速度越来越快,然后内存倒是没什么进展,这样的话CPU的读写操作就会非常耗时,效率不就很低了? 所以这个时候就出现了高速缓存(Cache)来解决这个问题,那么缓存是什么呢?缓

深入理解Java内存模型(四)——volatile

volatile的特性 当我们声明共享变量为volatile后,对这个变量的读/写将会很特别.理解volatile特性的一个好方法是:把对volatile变量的单个读/写,看成是使用同一个锁对这些单个读/写操作做了同步.下面我们通过具体的示例来说明,请看下面的示例代码: class VolatileFeaturesExample { //使用volatile声明64位的long型变量 volatile long vl = 0L; public void set(long l) { vl = l;

深入理解Java内存模型(1 ) -- 基础(转载)

原文地址:http://www.infoq.com/cn/articles/java-memory-model-1 并发编程模型的分类 在并发编程中,我们需要处理两个关键问题:线程之间如何通信及线程之间如何同步(这里的线程是指并发执行的活动实体).通信是指线程之间以何种机制来交换信息.在命令式编程中,线程之间的通信机制有两种:共享内存和消息传递. 在共享内存的并发模型里,线程之间共享程序的公共状态,线程之间通过写-读内存中的公共状态来隐式进行通信.在消息传递的并发模型里,线程之间没有公共状态,线

深入理解Java内存模型-volatile

volatile的特性 当我们声明共享变量为volatile后,对这个变量的读/写将会很特别.理解volatile特性的一个好方法是:把对volatile变量的单个读/写,看成是使用同一个监视器锁对这些单个读/写操作做了同步.下面我们通过具体的示例来说明,请看下面的示例代码: class VolatileFeaturesExample { volatile long vl = 0L; //使用volatile声明64位的long型变量 public void set(long l) { vl =

深入理解Java内存模型之系列篇[转]

原文链接:http://blog.csdn.net/ccit0519/article/details/11241403 深入理解Java内存模型(一)——基础 并发编程模型的分类 在并发编程中,我们需要处理两个关键问题:线程之间如何通信及线程之间如何同步(这里的线程是指并发执行的活动实体).通信是指线程之间以何种机制来交换信息.在命令式编程中,线程之间的通信机制有两种:共享内存和消息传递. see:命令式编程.函数式编程 在共享内存的并发模型里,线程之间共享程序的公共状态,线程之间通过写-读内存

深入理解Java内存模型(一)——基础

并发编程模型的分类 在并发编程中,我们需要处理两个关键问题:线程之间如何通信及线程之间如何同步(这里的线程是指并发执行的活动实体).通信是指线程之间以何种机制来交换信息.在命令式编程中,线程之间的通信机制有两种:共享内存和消息传递. 在共享内存的并发模型里,线程之间共享程序的公共状态,线程之间通过写-读内存中的公共状态来隐式进行通信.在消息传递的并发模型里,线程之间没有公共状态,线程之间必须通过明确的发送消息来显式进行通信. 同步是指程序用于控制不同线程之间操作发生相对顺序的机制.在共享内存并发

深入理解Java内存模型(七)——总结

处理器内存模型 顺序一致性内存模型是一个理论参考模型,JMM和处理器内存模型在设计时通常会把顺序一致性内存模型作为参照.JMM和处理器内存模型在设计时会对 顺序一致性模型做一些放松,因为如果完全按照顺序一致性模型来实现处理器和JMM,那么很多的处理器和编译器优化都要被禁止,这对执行性能将会有很大的影 响. 根据对不同类型读/写操作组合的执行顺序的放松,可以把常见处理器的内存模型划分为下面几种类型: 放松程序中写-读操作的顺序,由此产生了total store ordering内存模型(简称为TS