深入理解happens-before和as-if-serial语义

本文大部分整理自《Java并发编程的艺术》,温故而知新,加深对基础的理解程度。下面可以和小编来一起学习下

概述

本文大部分整理自《Java并发编程的艺术》,温故而知新,加深对基础的理解程度。

指令序列的重排序

我们在编写代码的时候,通常自上而下编写,那么希望执行的顺序,理论上也是逐步串行执行,但是为了提高性能,编译器和处理器常常会对指令做重排序。

1) 编译器优化的重排序。编译器在不改变单线程程序语义的前提下,可以重新安排语句的执行顺序。
2) 指令级并行的重排序。现代处理器采用了指令级并行技术来将多条指令重叠执行。如果不存在数据依赖性,处理器可以改变语句对应机器指令的执行顺序。
3) 内存系统的重排序。由于处理器使用缓存和读/写缓冲区,这使得加载和存储操作看上去可能是在乱序执行。

从Java源代码到最终实际执行的指令序列,会分别经历下面3种重排序:


2019052111184513.pnguploading.4e448015.gif转存失败重新上传取消

happens-before语义

从JDK 5开始,Java使用新的内存模型,使用happens-before的概念来阐述操作之间的内存可见性。那到底什么是happens-before呢?

在JMM中,如果一个操作执行的结果需要对另一个操作可见,那么这两个操作之间必须要存在happens-before关系,这里提到的两个操作既可以是在一个线程之内,也可以是在不同线程之间。

happens-before规则如下:

程序顺序规则: 对于单个线程中的每个操作,前继操作happens-before于该线程中的任意后续操作。
监视器锁规则: 对一个锁的解锁,happens-before于随后对这个锁的加锁。
volatile变量规则: 对一个volatile域的写,happens-before于任意后续对这个volatile域的读。
传递性: 如果A happens-before B,且B happens-before C,那么A happens-before C。

注意:

两个操作之间具有happens-before关系,并不意味着前一个操作必须要在后一个操作之前执行,happens-before仅仅要求前一个操作(执行的结果)对后一个操作可见,且前一个操作按顺序排在第二个操作之前。

happens-before与JMM的关系如图所示:
2019052111184514.pnguploading.4e448015.gif转存失败重新上传取消

如图所示,一个happens-before规则对应于一个或多个编译器和处理器重排序规则。

重排序

重排序指的是:编译器和处理器为了优化程序性能而对指令序列进行重新排序的一种手段。

如果两个操作访问同一个变量,且这两个操作中有一个为写操作,此时这两个操作之间就存在数据依赖性。数据依赖分为下列3种类型:
2019052111184515.pnguploading.4e448015.gif转存失败重新上传取消
上面情况,只要重排序两个操作的执行顺序,程序的执行结果就会被改变。而编译器和处理器可能会对操作做重排序,但是编译器和处理器在重排序时,会遵守数据依赖性,编译器和处理器不会改变存在数据依赖关系的两个操作的执行顺序。

注意:

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

as-if-serial语义

as-if-serial语义的意思是:不管怎么重排序,单线程程序的执行结果不能被改变。编译器、runtime和处理器都必须遵守as-if-serial语义。所以编译器和处理器不会对存在数据依赖关系的操作做重排序,因为这种重排序会改变执行结果。但是,如果操作之间不存在数据依赖关系,这些操作就可能被编译器和处理器重排序。

下面还是以书中的实例(计算圆的面积)进行说明:

double pi = 3.14;  
// Adouble r = 1.0;  
// Bdouble area = pi * r * r; // C

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

2019052111184516.pnguploading.4e448015.gif转存失败重新上传取消

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

该程序的两种可能执行顺序:

2019052111184517.pnguploading.4e448015.gif转存失败重新上传取消

as-if-serial语义把单线程程序保护了起来,遵守as-if-serial语义的编译器、runtime和处理器共同为编写单线程程序的程序员创建了一个幻觉:单线程程序是按程序的顺序来执行的。

程序顺序规则

根据happens-before的程序顺序规则,上面计算圆的面积的示例代码存在3个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之前执行,JMM并不要求A一定要在B之前执行。JMM仅仅要求前一个操作(执行的结果)对后一个操作可见,且前一个操作按顺序排在第二个操作之前。这里操作A的执行结果不需要对操作B可见,而且重排序操作A和操作B后的执行结果,与操作A和操作B按happens-before顺序执行的结果一致。在这种情况下,JMM会认为这种重排序并不非法,JMM允许这种重排序。

重排序对多线程的影响

重排序是否会改变多线程程序的执行结果?还是借用书中的一个例子:

class ReorderExample {int a = 0;boolean flag = false;public void writer() {a = 1;   // 1flag = true;  // 2}public void reader() {if (flag) {  // 3int i = a * a; // 4}}
}

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

答案是:不一定能看到。

由于操作1和操作2没有数据依赖关系,编译器和处理器可以对这两个操作重排序;同样,操作3和操作4没有数据依赖关系,编译器和处理器也可以对这两个操作重排序。

当操作1和操作2重排序时,可能会产生什么效果?(虚箭线标识错误的读操作,用实箭线标识正确的读操作。)

2019052111184518.pnguploading.4e448015.gif转存失败重新上传取消

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

当操作3和操作4重排序时会产生什么效果。下面是操作3和操作4重排序后,程序执行的时序图:

2019052111184619.pnguploading.4e448015.gif转存失败重新上传取消

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

注意:

在单线程程序中,对存在控制依赖的操作重排序,不会改变执行结果。
在多线程程序中,对存在控制依赖的操作重排序,可能会改变程序的执行结果。

参考

《Java并发编程的艺术》

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。

您可能感兴趣的文章:

  • volatile与happens-before的关系与内存一致性错误
  • 深入浅出了解happens-before原则
  • 简单易懂讲解happens-before原则
  • Java内存之happens-before和重排序
  • 浅谈Java内存模型之happens-before

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/438325.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

开源SPL重新定义OLAP Server

OLAP(Online Analytical Processing)是指在线联机分析,基于数据查询计算并实时获得返回结果。日常业务中的报表、数据查询、多维分析等一切需要即时返回结果的数据查询任务都属于OLAP的范畴。对应的,行业内也有相应产品来满足这类…

平层、错层、跃层、复式、loft的区别是什么?

平层正如字面上的理解,是所有功能厅都在同一个水平面上。平时我们所见的户型,都是平层。错层室内各功能用房在不同的平面上,用2-4个台阶进行隔断。跃层是两层的住宅,在室内会设计一条楼梯连接上下两层,功能区会分开。复…

C#中集合接口关系笔记

IEnumerable IEnumerable接口是所有集合类型的祖宗接口,其作用相当于Object类型之于其它类型。如果某个类型实现了IEnumerable接口,就意味着它可以被迭代访问,也就可以称之为集合类型(可枚举); ICollecti…

C#集合类型总结和性能分析

C#集合类型概述 集合是.NET FCL(Framework Class Library)中很重要的一部分。所有的集合类都继承自IEnumerable。集合类总体可分为一下几类:关联/非关联型集合,顺序/随机访问集合,顺序/无序集合,泛型/非泛型集合,线程…

为什么TypedReference在幕后

我发现的其他用途TypedReference: C#中的“专门化”泛型(这是类型安全的)&#xff1a; static void foo<T>(ref T value) {//This is the ONLY way to treat value as int, without boxing/unboxing objectsif (value is int){ __refvalue(__makeref(value), int) 1; …

C# 中的可变参数方法(VarArgs)

首先需要明确一点&#xff1a;这里提到的可变参数方法&#xff0c;指的是具有 CallingConventions.VarArgs 调用约定的方法&#xff0c;而不是包含 params 参数的方法。可以通过MethodBase.CallingConvention 属性来获取某个方法的调用约定。 举个常见的例子来说&#xff0c;C…

Spring Boot Actuator监控关闭

可以使用如下属性:management.endpoints.enabled-by-defaultfalse * 在YAML中有特殊的含义&#xff0c;所以如果想使用include或者exclude包含所有的端点时要加上引号&#xff0c;如下示例&#xff1a; # 暴露监控端点 management:endpoints:enabled-by-default: false #关闭…

Javascript获取类名方法

函数&#xff1a; entity.getClassName function(obj) { if (obj && obj.constructor && obj.constructor.toString()) { if(obj.constructor.name) { return obj.constructor.name; } let str…

mysql把一个数据库中的数据复制到另一个数据库中的表 2个表结构相同

1。表结构相同的表&#xff0c;且在同一数据库&#xff08;如&#xff0c;table1,table2) Sql &#xff1a;insert into table1 select * from table2 (完全复制) insert into table1 select distinct * from table2(不复制重复纪录&#xff09; insert into table1 select …

!Spring Aop中四个重要概念,切点,切面,连接点,通知

一、基本概念 1. 通知&#xff1a; 就是我们编写的希望Aop时额外执行的那个方法。我们通过Aop希望我们编写的方法在目标方法执行前执行&#xff0c;或者执行后执行。 2. 切点&#xff1a;切点就是我们配置的满足我们条件的目标方法。比如我们规定&#xff1a;名字前面是select…

Spring AOP(通知、连接点、切点、切面)

一、AOP术语 通知&#xff08;Advice&#xff09;   切面的工作被称为通知。通知定义了切面是什么以及何时使用。除了描述切面要完成的工作&#xff0c;通知还解决了何时执行这个工作的问题。 5种通知类型&#xff1a;前置通知&#xff08;Before&#xff09;&#xff1a;在…

Map集合根据key,value排序

/*** 根据map的key排序* * param map 待排序的map* param isDesc 是否降序&#xff0c;true&#xff1a;降序&#xff0c;false&#xff1a;升序* return 排序好的map*/public static <K extends Comparable<? super K>, V> Map<K, V> sortByKey(Map<K, …

C#中几种常用的集合的用法

集合:将一推数据类型相同的数据放入到一个容器内&#xff0c;该容器就是数组&#xff1a;内存中开辟的一连串空间。 非泛型集合 ArrayList集合&#xff1a; ArrayList是基于数组实现的&#xff0c;是一个动态数组&#xff0c;其容量能自动 增长 ArrayList的命名空间System.…

oracle新增,删除字段,设置默认值

增加字段 alter table 表 Add 字段 varchar2(128); 设置字段默认值为时间戳 alter table 表 modify 字段 default sysdata; 删除字段 alter table 表 drop column 字段;

C#使用Redis的基本操作

一&#xff0c;引入dll 1.ServiceStack.Common.dll 2.ServiceStack.Interfaces.dll 3.ServiceStack.Redis.dll 4.ServiceStack.Text.dll 二&#xff0c;修改配置文件 在你的配置文件中加入如下的代码&#xff1a; <appSettings><add key"RedisPath" value…

Navicat将mysql表结构导成oracle表结构

1&#xff0c;选中对应的表右键逆向表到模型 2.点击右上角文件转换模型为 3.模型选择物理&#xff0c;数据库oracle&#xff0c;选择对应的版本 4.新弹出的模型点击右上角文件&#xff0c;导出sql 5.选择路径导出sql

程序员们的三高:高并发、高性能、高可用

你们知道淘宝&#xff0c;京东这些购物商场吗&#xff1f;他们到了双11&#xff0c;双12为什么能支持全国14亿人口同时购物下单呢&#xff0c;因为他们的程序做到了高并发、高性能、高可用。那么你对程序员的三高了解多少呢&#xff1f; 高并发 一. 高并发 高并发是现在互联…

C#接口归纳总结实例详解

本篇文章通过实例代码对接口做了详解&#xff0c;需要的朋友可以参考下 C#接口的学习&#xff0c;在编程中&#xff0c;我们经常会用到接口&#xff0c;那什么是接口呢&#xff1f; 接口描述的是可属于任何类或结构的一组相关功能&#xff0c;所以实现接口的类或结构必须实现…

char 和 varchar 的区别,数据库索引B+树

char 和 varchar 的区别 char(n) &#xff1a;固定长度&#xff0c;效率高&#xff1b;缺点&#xff1a;占用空间&#xff1b;存储固定长度的&#xff0c;使用 char 非常合适。 varchar(n) &#xff1a;可变长度&#xff0c;存储的值是每个值占用的字节再加上一个用来记录其长…

日常收集C#接口知识(知识全面)

本文分为七章节给大家介绍c#接口知识&#xff0c;内容比较详细&#xff0c;特此分享脚本之家平台&#xff0c;供大家参考 第一节 接口慨述 接口&#xff08;interface&#xff09;用来定义一种程序的协定。实现接口的类或者结构要与接口的定义严格一致。有了这个协定&#xff…