对于本篇文章,将从四个概念来介绍:内存模型基础,重排序,顺序一致性和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. Java开发笔记(六十)匿名内部类的优势

    前面依次介绍了简单接口和扩展接口,给出的范例都是自定义的接口代码,其实Java系统本身就自带了若干行为接口,为了更好地理解系统接口的详细用法,接下来还是从一个基础的例子出发,抽丝剥茧地逐步说明接口的几 ...

  2. java数组及数组的插入,删除,冒泡算法

    1.数组的定义 数组为相同类型的若干个数据,在一个数组里面,不能存放多种不同类型的数据,其中每个数据为该数组的一个元素,可以通过下标对改元素进行访问. 1.1 数组的特点 (1)数组被创建后,长度就已 ...

  3. AI书单

    1.<TensorFlow实战>   黄文坚 2.<Machine Learning> [美]Tom Mitchell 3.<机器学习> 周志华  

  4. Cookie的一点认识!

    使用cookie的时间不短了,但是每次都是用一点查一点,也没有总结过.趁着项目不忙的间隙,总结一下.首先说为什么要使用cookie,什么是cookie cookie是用户访问一些网站后,浏览器生成的给 ...

  5. 让priority_queue支持小根堆的几种方法

    点击这里了解什么是priority_queue 前言 priority_queue默认是大根堆,也就是大的元素会放在前面 例如 #include<iostream> #include< ...

  6. C# 使用NPOI出现超过最大字体数和单元格格式变成一样的解决

    在使用NPOI写入Excel文件的时候出现“它已经超出最多允许的字体数”,查询资料发现是字体创建太多的原因,需要将常用字体创建好,传入CellStyle中.参考(http://www.cnblogs. ...

  7. 如何解决分配到Autoconfiguration IPV4 地址

    配置完服务器静态IP后,在CMD窗口中查看ip地址,发现是Autoconfiguration IPV4. 上网搜索了,是关于虚拟服务器的,但是我没有配置虚拟服务器,有点奇怪. 使用下面的教程,可以解决 ...

  8. Linix基本命令

    基本命令关机:shutdown -h halt init 0 poweroff重启:shutdown -r reboot init 6pwd:查看工作目录ls:查看指定目录的内容-l:列表显示-a:显 ...

  9. Django contenttypes 组件

    contenttypes组件 介绍 Django包含一个contenttypes应用程序(app),可以跟踪Django项目中安装的所有模型(Model),提供用于处理模型的高级通用接口. Conte ...

  10. maven-assembly-plugin打包可执行的jar包

    pom.xml添加 <build> <plugins> <plugin> <artifactId>maven-assembly-plugin</a ...