`

【转】深入理解Java内存模型(二)——重排序

阅读更多

数据依赖性

如果两个操作访问同一个变量,且这两个操作中有一个为写操作,此时这两个操作之间就存在数据依赖性。数据依赖分下列三种类型:

名称 代码示例 说明
写后读 a = 1;b = a; 写一个变量之后,再读这个位置。
写后写 a = 1;a = 2; 写一个变量之后,再写这个变量。
读后写 a = b;b = 1; 读一个变量之后,再写这个变量。

上面三种情况,只要重排序两个操作的执行顺序,程序的执行结果将会被改变。

前面提到过,编译器和处理器可能会对操作做重排序。编译器和处理器在重排序时,会遵守数据依赖性,编译器和处理器不会改变存在数据依赖关系的两个操作的执行顺序。

注意,这里所说的数据依赖性仅针对单个处理器中执行的指令序列和单个线程中执行的操作,不同处理器之间和不同线程之间的数据依赖性不被编译器和处理器考虑。

as-if-serial语义

as-if-serial语义的意思指:不管怎么重排序(编译器和处理器为了提高并行度),(单线程)程序的执行结果不能被改变。编译器,runtime 和处理器都必须遵守as-if-serial语义。

为了遵守as-if-serial语义,编译器和处理器不会对存在数据依赖关系的操作做重排序,因为这种重排序会改变执行结果。但是,如果操作之间不存在数据依赖关系,这些操作可能被编译器和处理器重排序。为了具体说明,请看下面计算圆面积的代码示例:

double pi  = 3.14;    //A
double r   = 1.0;     //B
double area = pi * r * r; //C

上面三个操作的数据依赖关系如下图所示:

如上图所示,A和C之间存在数据依赖关系,同时B和C之间也存在数据依赖关系。因此在最终执行的指令序列中,C不能被重排序到A和B的前面(C排到A和B的前面,程序的结果将会被改变)。但A和B之间没有数据依赖关系,编译器和处理器可以重排序A和B之间的执行顺序。下图是该程序的两种执行顺序:

as-if-serial语义把单线程程序保护了起来,遵守as-if-serial语义的编译器,runtime 和处理器共同为编写单线程程序的程序员创建了一个幻觉:单线程程序是按程序的顺序来执行的。as-if-serial语义使单线程程序员无需担心重排序会干扰他们,也无需担心内存可见性问题。

程序顺序规则

根据happens- before的程序顺序规则,上面计算圆的面积的示例代码存在三个happens- before关系:

  1. A happens- before B;
  2. B happens- before C;
  3. A happens- before C;

这里的第3个happens- before关系,是根据happens- before的传递性推导出来的。

这里A happens- before B,但实际执行时B却可以排在A之前执行(看上面的重排序后的执行顺序)。在第一章提到过,如果A happens- before B,JMM并不要求A一定要在B之前执行。JMM仅仅要求前一个操作(执行的结果)对后一个操作可见,且前一个操作按顺序排在第二个操作之前。这里操作A的执行结果不需要对操作B可见;而且重排序操作A和操作B后的执行结果,与操作A和操作B按happens- before顺序执行的结果一致。在这种情况下,JMM会认为这种重排序并不非法(not illegal),JMM允许这种重排序。

在计算机中,软件技术和硬件技术有一个共同的目标:在不改变程序执行结果的前提下,尽可能的开发并行度。编译器和处理器遵从这一目标,从happens- before的定义我们可以看出,JMM同样遵从这一目标。

重排序对多线程的影响

现在让我们来看看,重排序是否会改变多线程程序的执行结果。请看下面的示例代码:

class ReorderExample {
int a = 0;
boolean flag = false;

public void writer() {
    a = 1;                   //1
    flag = true;             //2
}

Public void reader() {
    if (flag) {                //3
        int i =  a * a;        //4
        ……
    }
}
}

flag变量是个标记,用来标识变量a是否已被写入。这里假设有两个线程A和B,A首先执行writer()方法,随后B线程接着执行reader()方法。线程B在执行操作4时,能否看到线程A在操作1对共享变量a的写入?

答案是:不一定能看到。

由于操作1和操作2没有数据依赖关系,编译器和处理器可以对这两个操作重排序;同样,操作3和操作4没有数据依赖关系,编译器和处理器也可以对这两个操作重排序。让我们先来看看,当操作1和操作2重排序时,可能会产生什么效果?请看下面的程序执行时序图:

如上图所示,操作1和操作2做了重排序。程序执行时,线程A首先写标记变量flag,随后线程B读这个变量。由于条件判断为真,线程B将读取变量a。此时,变量a还根本没有被线程A写入,在这里多线程程序的语义被重排序破坏了!

※注:本文统一用红色的虚箭线表示错误的读操作,用绿色的虚箭线表示正确的读操作。

下面再让我们看看,当操作3和操作4重排序时会产生什么效果(借助这个重排序,可以顺便说明控制依赖性)。下面是操作3和操作4重排序后,程序的执行时序图:

在程序中,操作3和操作4存在控制依赖关系。当代码中存在控制依赖性时,会影响指令序列执行的并行度。为此,编译器和处理器会采用猜测(Speculation)执行来克服控制相关性对并行度的影响。以处理器的猜测执行为例,执行线程B的处理器可以提前读取并计算a*a,然后把计算结果临时保存到一个名为重排序缓冲(reorder buffer ROB)的硬件缓存中。当接下来操作3的条件判断为真时,就把该计算结果写入变量i中。

从图中我们可以看出,猜测执行实质上对操作3和4做了重排序。重排序在这里破坏了多线程程序的语义!

在单线程程序中,对存在控制依赖的操作重排序,不会改变执行结果(这也是as-if-serial语义允许对存在控制依赖的操作做重排序的原因);但在多线程程序中,对存在控制依赖的操作重排序,可能会改变程序的执行结果。

参考文献

  1. Computer Architecture: A Quantitative Approach, 4th Edition
  2. Concurrent Programming on Windows
  3. Concurrent Programming in Java™: Design Principles and Pattern
  4. JSR-133: Java Memory Model and Thread Specification
  5. JSR 133 (Java Memory Model) FAQ

关于作者

程晓明,Java软件工程师,国家认证的系统分析师、信息项目管理师。专注于并发编程,就职于富士通南大。个人邮箱:asst2003@163.com

 

转自:http://www.infoq.com/cn/articles/java-memory-model-2?utm_source=infoq&utm_medium=related_content_link&utm_campaign=relatedContent_articles_clk

分享到:
评论

相关推荐

    JAVA上百实例源码以及开源项目

     基于JAVA的UDP服务器模型源代码,内含UDP服务器端模型和UDP客户端模型两个小程序,向JAVA初学者演示UDP C/S结构的原理。 简单聊天软件CS模式 2个目标文件 一个简单的CS模式的聊天软件,用socket实现,比较简单。 ...

    JAVA上百实例源码以及开源项目源代码

     基于JAVA的UDP服务器模型源代码,内含UDP服务器端模型和UDP客户端模型两个小程序,向JAVA初学者演示UDP C/S结构的原理。 简单聊天软件CS模式 2个目标文件 一个简单的CS模式的聊天软件,用socket实现,比较简单。 ...

    数据结构与算法分析Java语言描述(第二版)

    堆6.6 左式堆6.6.1 左式堆性质6.6.2 左式堆操作6.7 斜堆6.8 二项队列6.8.1 二项队列结构6.8.2 二项队列操作6.8.3 二项队列的实现6.9 标准库中的优先队列小结练习参考文献第7章 排序7.1 预备知识7.2 插入排序7.2.1...

    数据结构与算法分析_Java语言描述(第2版)]

    堆6.6 左式堆6.6.1 左式堆性质6.6.2 左式堆操作6.7 斜堆6.8 二项队列6.8.1 二项队列结构6.8.2 二项队列操作6.8.3 二项队列的实现6.9 标准库中的优先队列小结练习参考文献第7章 排序7.1 预备知识7.2 插入排序7.2.1 ...

    java初学者必看

    最近正在学习Java,也买了很多的有关Java方面的书籍,其中发现《跟我学Java》这本书,都的很不错啊,所以顺便拿电脑把这本书的目录敲了下来,与大家分享。尤其是那些和我一样初学Java的朋友们,看看哪一节对你有用,...

    编程珠玑 第二版 修订版

    9.3 大手术——二分搜索 88 9.4 原理 91 9.5 习题 92 9.6 深入阅读 94 第10章 节省空间 95 10.1 关键在于简单 95 10.2 示例问题 96 10.3 数据空间技术 99 10.4 代码空间技术 101 10.5 原理 103 10.6 习题...

    合肥贞龙JAVA版本B2B电子商务平台

     轻量级框架,易用易理解,方便二次开发  支持多种数据库存储,随时切换异构数据库,兼容原有系统  LINUX、JAVA从底层确保系统的安全性、提高防毒防黑能力  为SNS、CRM、SCM等系统提供了全面的接口和灵活的...

    数据结构与算法分析_Java语言描述(第2版)

    《数据结构与算法分析:Java语言描述(第2版)》把算法分析与最有效率的Java程序的开发有机地结合起来,深入分析每种算法,内容全面、缜密严格,并细致讲解精心构造程序的方法。 内容截图 目录: 译者序 前言 ...

    数据结构与算法分析 Java语言描述第2版

    堆6.6 左式堆6.6.1 左式堆性质6.6.2 左式堆操作6.7 斜堆6.8 二项队列6.8.1 二项队列结构6.8.2 二项队列操作6.8.3 二项队列的实现6.9 标准库中的优先队列小结练习参考文献第7章 排序7.1 预备知识7.2 插入排序7.2.1 ...

    Java数据库编程宝典3

    目录 前言 第1部分 介绍数据库、SQL和JDBC 第1章 关系型数据库 1.1 理解关系型数据库管理系统 1.1.1 关系模型 1.1.2 Codd法则 1.1.3 表、行、列和关键字 ... 17.3 使用Java XML API——Xerces和JDOM ...

    数据结构与算法分析-Java语言描述(第2版)_2_2

    7.7.4 实际的快速排序例程 7.7.5 快速排序的分析 7.7.6 选择问题的线性期望时间算法 7.8 排序算法的一般下界 7.9 桶式排序 7.10 外部排序 7.10.1 为什么需要一些新的算法 7.10.2 外部排序模型 ...

    数据结构与算法分析-Java语言描述(第2版)_1_2

    7.7.4 实际的快速排序例程 7.7.5 快速排序的分析 7.7.6 选择问题的线性期望时间算法 7.8 排序算法的一般下界 7.9 桶式排序 7.10 外部排序 7.10.1 为什么需要一些新的算法 7.10.2 外部排序模型 ...

    Java开发技术大全 电子版

    Java开发技术大全 电子版 第1篇Java基础知识入门. 第1章Java的开发运行环境2 1.1Java的运行环境与虚拟机2 1.2Java的开发环境4 1.2.1JDK的安装4 1.2.2如何设置系统环境变量6 1.2.3编译命令的使用8 1.2.4解释...

    Java语言完成UDP协议编制程序

    在OSI模型中,在第四层——传输层,处于IP协议的上一层。UDP有不提供数据报分组、组装和不能对数据包的排序的缺点,也就是说,当报文发送之后,是无法得知其是否安全完整到达的。它不属于连接型协议,因而具有资源...

    Java数据库编程宝典2

    目录 前言 第1部分 介绍数据库、SQL和JDBC 第1章 关系型数据库 1.1 理解关系型数据库管理系统 1.1.1 关系模型 1.1.2 Codd法则 1.1.3 表、行、列和关键字 ... 17.3 使用Java XML API——Xerces和JDOM ...

    Java数据库编程宝典4

    目录 前言 第1部分 介绍数据库、SQL和JDBC 第1章 关系型数据库 1.1 理解关系型数据库管理系统 1.1.1 关系模型 1.1.2 Codd法则 1.1.3 表、行、列和关键字 ... 17.3 使用Java XML API——Xerces和JDOM ...

    Java数据库编程宝典1

    目录 前言 第1部分 介绍数据库、SQL和JDBC 第1章 关系型数据库 1.1 理解关系型数据库管理系统 1.1.1 关系模型 1.1.2 Codd法则 1.1.3 表、行、列和关键字 ... 17.3 使用Java XML API——Xerces和JDOM ...

    Java中实现UDP协议编程的方法

    在OSI模型中,在第四层——传输层,处于IP协议的上一层。UDP有不提供数据报分组、组装和不能对数据包的排序的缺点,也就是说,当报文发送之后,是无法得知其是否安全完整到达的。 为什么要使用UDP 在选择使用协议...

Global site tag (gtag.js) - Google Analytics