对于本篇文章,将从四个概念来介绍:内存模型基础,重排序,顺序一致性和happens-before

1.内存模型基础

在并发编程中,有两个关键问题:线程之间如何通信和如何同步。由此而引出了两种并发模型:共享内存的并发模型和消息传递的并发模型。

1.1 消息传递的并发模型

该模型是指两个线程之间通过发送消息来进行显式的通信,而同步则是隐式进行的,因为发送消息的动作要先于接收消息。go语言采用的就是这种并发模型。

1.2 共享内存的并发模型

该模型是两个线程之间通过共享内存中的公共状态,然后读写公共状态来进行隐式通信的,而同步则需要显式的进行控制。java语言采用的就是共享内存的并发模型。

java线程之间的通信是由java内存模型(JMM)控制,它定义了主内存和线程之间的抽象关系:线程之间的共享变量存储在主内存中,每个线程都有一个私有的本地内存,本地内存存储了该线程读/写共享变量的副本,如下图:

如图,如果线程A和线程B想要通信,需要经过以下步骤:

  • 线程A把本地内存中更新过的共享变量的副本刷新到主内存中
  • 线程B到主内存中读取线程A已经更新过的共享变量

如此一来,线程A和线程B就通过主内存进行间接的通信了。这就是java内存模型的概念。

2.重排序

2.1 重排序概念

重排序是指编译器和处理器为了优化程序性能而对指令序列进行重新排序的一种手段。通过下面一个例子来说明:

  处理器A 处理器B
代码

a = 1;   //A1

x = b;   //A2

b = 2;   //B1

y = a;   //B2

运行结果

初始状态:a = b = 0

处理器执行后可能得到的结果:x = y = 0

假设处理器A和处理器B按程序的顺序并行执行代码,可能得到的结果是x = y = 0;为什么呢?这就涉及到了重排序。我们知道处理器使用了写缓冲区临时保存了向内存中写入的数据,比如在步骤A1中,准确的说是分为了两个步骤 A1-1:把a = 1 保存到写缓冲区中,A1-2:把 a = 1 从缓冲区刷新到主内存中,只有这两个步骤都完成了,才可以说A1步骤完成了。考虑一下这种情况:A1-1 ===》A2 ===》A1-2,也就是说内存实际发生顺序变为了 A2 ===》A1,如果处理器B也是如此,此时得到的结果就是x = y = 0;因为处理器执行内存操作的顺序和内存操作实际发生的顺序不一致,这就是重排序。

2.2 重排序遵循的原则

2.2.1 数据依赖性

数据依赖性定义:如果同一个处理器和同一个线程的两个操作访问同一个变量,并且这两个操作中有一个是写操作,此时这两个操作之间就存在数据依赖性。数据依赖性有三种类型:

名称 代码示例 说明
写后读

a = 1;

b = a;

写一个变量之后,再读这个变量
写后写

a = 1;

b = 2;

写这个变量之后,再写这个变量
读后写

a = b;

b = 1;

读一个变量之后,再写这个变量

编译器和处理器可能会对操作做重排序,但是会遵守数据依赖性,不会改变存在数据依赖关系的两个操作的执行顺序。这里的数据依赖性是指单个处理器和单个线程中执行的操作。

2.2.2 遵守as-if-serial语义

as-if-serial的语义是:无论怎么重排序,单线程程序的执行结果是不能改变的。为了遵守as-if-serial语义,编译器和处理器不会对存在数据依赖关系的操作做重排序。

3. 顺序一致性内存模型

顺序一致性内存模型,是一个理想化的理论参考模型,它提供了极强的内存可见性。它有两大特征:

  • 一个线程中所有的操作必须按照程序的顺序来执行
  • 不管程序是否同步,所有线程都只能看到一个单一的操作执行顺序,在顺序一致性模型中,每个操作必须是原子执行且立刻对所有线程可见。

4.happens-before

happens-before是JMM的核心概念,JMM的设计者,在设计JMM时考虑两个因素:从程序员的角度,使用JMM时希望是一个强内存模型,易于使用;从编译器和处理器的角度,希望JMM是一个弱内存模型,对它的束缚越少越好,这样它能最大程度的优化来提高性能。

最终,JMM遵循了一个原则:在不改变程序执行结果(单线程程序和正确同步的多线程程序)的前提下,处理器和编译器怎么优化都可以。

4.1 happens-before的定义

  • 如果一个操作happens-before另外一个操作,那么第一个操作的执行结果对第二个操作可见,而且第一个操作的执行顺序排在第二个操作之前。
  • 两个操作存在happens-before关系,并不意味着java平台的具体实现必须要按照happens-before关系指定的顺序来执行。如果重排序之后的执行结果和按照happens-before关系来执行的结果一致,那么JMM允许这种重排序。

4.2 happens-before规则

  • 程序顺序规则:一个线程中的每个操作,happens-before于该线程中的任意后续操作。
  • 监视器锁规则:对一个锁的解锁,happens-before于随后对这个锁的加锁。
  • volatile变量规则:对一个volatile域的写,happens-before于任意后续对这个volatile域的读。
  • 传递性:如果A happens-before B,B happens-before C,那么 A happens-before C
  • start()规则:如果线程A执行操作ThreadB.start()(启动线程B),那么A线程的ThreadB.start()操作happens-before于线程B中的任意操作。
  • join()规则:如果线程A执行操作ThreadB.join()并成功返回,那么线程B的任意操作Happens-before于线程A从ThreadB.join()操作成功返回。

参考《java并发编程的艺术》

java内存模型详解的更多相关文章

  1. 云时代架构阅读笔记六——Java内存模型详解(二)

    承接上文:云时代架构阅读笔记五——Java内存模型详解(一) 原子性.可见性.有序性 Java内存模型围绕着并发过程中如何处理原子性.可见性和有序性这三个特征来建立的,来逐个看一下: 1.原子性(At ...

  2. Java 内存模型详解

    概述 Java的内存模型(Java Memory Model )简称JMM.首先应该明白,Java内存模型是一个规范,主要规定了以下两点: 规定了一个线程如何以及何时可以看到其他线程修改过后的共享变量 ...

  3. 云时代架构阅读笔记五——Java内存模型详解(一)

    什么是Java内存模型 Java虚拟机规范中试图定义一种Java内存模型(Java Memory Model,JMM)来屏蔽掉各种硬件和操作系统的访问差异,以实现让Java程序在各种平台下都能达到一致 ...

  4. 7.Java内存模型详解

    https://blog.csdn.net/qq_37141773/article/details/103138476 一.虚拟机 同样的java代码在不同平台生成的机器码肯定是不一样的,因为不同的操 ...

  5. Java内存结构详解

    Java内存结构详解 Java把内存分成:栈内存,堆内存,方法区,本地方法区和寄存器等. 下面分别介绍栈内存,堆内存,方法区各自一些特性: 1.栈内存 (1)一些基本类型的变量和对象的引用变量都是在函 ...

  6. flink内存模型详解与案例

    任务提交时的一些yarn设置(通用客户端模式) 指定并行度                        -p 5 \ 指定yarn队列                     -Dyarn.appl ...

  7. JMM内存模型详解(一)

    本文开始死磕JMM(Java内存模型)由于知识点较多,分来写 该文为JMM第一篇 技术往往是枯燥的,本文文字较多 1. JMM是什么? 其实JMM很好理解,我简单的解释一下,在Java多线程中我们经常 ...

  8. Java虚拟机:内存模型详解

    版权声明:本文为博主原创文章,转载请注明出处,欢迎交流学习! 我们都知道,当虚拟机执行Java代码的时候,首先要把字节码文件加载到内存,那么这些类的信息都存放在内存中的哪个区域呢?当我们创建一个对象实 ...

  9. [转]Java内存溢出详解及解决方案

    原文地址:http://blog.csdn.net/xianmiao2009/article/details/49254391 内存溢出与数据库锁表的问题,可以说是开发人员的噩梦,一般的程序异常,总是 ...

随机推荐

  1. [MySQL] 联合索引与using index condition

    1.测试联合索引的最左原则的时候, 发现了5.6版本后的新特性Index Condition Pushdown 2.含义就是存储引擎层根据索引尽可能的过滤数据,然后在返回给服务器层根据where其他条 ...

  2. [PHP] 适配器模式的日常使用

    适配器模式就是将一个类的接口方法转换成我希望用的另一个方法 , 下面是个常见的用处 class Session{ public $mc; public function __construct(){ ...

  3. [leetcode](4.21)2. 按字典序排列最小的等效字符串

    给出长度相同的两个字符串:A 和 B,其中 A[i] 和 B[i] 是一组等价字符.举个例子,如果 A = "abc" 且 B = "cde",那么就有 'a' ...

  4. Building QGIS from source - step by step(随笔2)

    QT安装 在Windows上安装QGIS,首先需要安装VS,VS的版本根据需要的版本下载,注意QGIS版本与VS版本对应.另外QT下载安装也需要与VS版本的安装对应.本机系统装的VS10,对应QT版本 ...

  5. JSONObject.parseObject

    { "data":{ "shop_uid":"123"; “id”:"123" } } 将上面的json字符串转换为JS ...

  6. OpenCL的buffer以及sub-buffer

    buffer,sub-buffer和image对比 相同点:都是OCL memory对象 维度 特性关键词 buffer 一维 array of bytes sub-buffer 一维 views i ...

  7. mssql 存储过程调用另一个存储过程中的结果的方法分享

    转自:http://www.maomao365.com/?p=6801 摘要: 下文将分享"一个存储过程"中如何调用"另一个存储过程的返回结果",并应用到自身的 ...

  8. Yii2.0调用sql server存储过程并获取返回值

    1.首先展示创建sql server存储过程的语句,创建一个简单的存储过程,测试用. SET ANSI_NULLS ON GO SET QUOTED_IDENTIFIER ON GO CREATE P ...

  9. win10 iis注册.net framework 此操作系统版本不支持此选项。

    利用dism工具 执行下面命令才成功 dism /online /enable-feature /featurename:IIS-ISAPIFilter dism /online /enable-fe ...

  10. python爬虫 | 一条高效的学习路径

    数据是创造和决策的原材料,高质量的数据都价值不菲.而利用爬虫,我们可以获取大量的价值数据,经分析可以发挥巨大的价值,比如: 豆瓣.知乎:爬取优质答案,筛选出各话题下热门内容,探索用户的舆论导向. 淘宝 ...