java内存模型详解
对于本篇文章,将从四个概念来介绍:内存模型基础,重排序,顺序一致性和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内存模型详解的更多相关文章
- 云时代架构阅读笔记六——Java内存模型详解(二)
承接上文:云时代架构阅读笔记五——Java内存模型详解(一) 原子性.可见性.有序性 Java内存模型围绕着并发过程中如何处理原子性.可见性和有序性这三个特征来建立的,来逐个看一下: 1.原子性(At ...
- Java 内存模型详解
概述 Java的内存模型(Java Memory Model )简称JMM.首先应该明白,Java内存模型是一个规范,主要规定了以下两点: 规定了一个线程如何以及何时可以看到其他线程修改过后的共享变量 ...
- 云时代架构阅读笔记五——Java内存模型详解(一)
什么是Java内存模型 Java虚拟机规范中试图定义一种Java内存模型(Java Memory Model,JMM)来屏蔽掉各种硬件和操作系统的访问差异,以实现让Java程序在各种平台下都能达到一致 ...
- 7.Java内存模型详解
https://blog.csdn.net/qq_37141773/article/details/103138476 一.虚拟机 同样的java代码在不同平台生成的机器码肯定是不一样的,因为不同的操 ...
- Java内存结构详解
Java内存结构详解 Java把内存分成:栈内存,堆内存,方法区,本地方法区和寄存器等. 下面分别介绍栈内存,堆内存,方法区各自一些特性: 1.栈内存 (1)一些基本类型的变量和对象的引用变量都是在函 ...
- flink内存模型详解与案例
任务提交时的一些yarn设置(通用客户端模式) 指定并行度 -p 5 \ 指定yarn队列 -Dyarn.appl ...
- JMM内存模型详解(一)
本文开始死磕JMM(Java内存模型)由于知识点较多,分来写 该文为JMM第一篇 技术往往是枯燥的,本文文字较多 1. JMM是什么? 其实JMM很好理解,我简单的解释一下,在Java多线程中我们经常 ...
- Java虚拟机:内存模型详解
版权声明:本文为博主原创文章,转载请注明出处,欢迎交流学习! 我们都知道,当虚拟机执行Java代码的时候,首先要把字节码文件加载到内存,那么这些类的信息都存放在内存中的哪个区域呢?当我们创建一个对象实 ...
- [转]Java内存溢出详解及解决方案
原文地址:http://blog.csdn.net/xianmiao2009/article/details/49254391 内存溢出与数据库锁表的问题,可以说是开发人员的噩梦,一般的程序异常,总是 ...
随机推荐
- FastReport编程方式给Picture控件赋值
public Image BytesToImage(Byte[] buffer) { var ms = new MemoryStream(buffer, 0, buffer.Length); retu ...
- Sqlserver UrlEncode
Sqlserver UrlEncode if exists (select * from dbo.sysobjects where id = object_id(N'[dbo].[UrlEncode ...
- 冒泡排序/选择排序/插入排序(c#)
---恢复内容开始--- 每次看这些排序都像没见过一样,完全理解不了,可是不久前明明了解的十分透彻.记下来记下来记下来! 1>>>冒泡排序:相邻的两两相比 把大的(或者小的)放后边, ...
- 关于Android使用SFTP上传文件报错问题
错误报告java.lang.NoClassDefFoundError:Failed resolution of: Lorg/ietf/jgss/Oid; 问题是由于 Lorg.ietf. jgss.O ...
- JavaScript 包装对象
万物皆对象 在JavaScript里,万物皆对象.但是某些对象有别于其它对象,我们可以用 typeof 来获取一个对象的类型,它总是返回一个字符串. typeof 123; // 'number' t ...
- Visual Studio无法调试
一.最近Visual studio调试不起来,运行完报错 二.解决方法 打开 调试>>>>选项>>>>常规>>>对ASP.NET启用 ...
- 我的新书《Android App开发从入门到精通》终于出版啦
前言 经过了两年多终于完成了这本书,2016年9月份开始写的,到今天为止2年零2个月,本书的内容大部分是去年完成的,看过我去年总结的读者可能知道,去年事情很多太忙了,导致本命年这本书没有上架(有点小小 ...
- 测者的性能测试手册:Web压力测试工具webbench
webbench最多可以模拟3万个并发连接去测试网站的负载能力,个人感觉要比Apache自带的ab压力测试工具好,安装使用也特别方便. 1.适用系统:Linux 2.编译安装: wget http:/ ...
- ADOConnection断线重连
问题: ADOConnection断线重连问题描述: 使用ADOConnection连接oracle数据库,开始正常,当网络断开时数据库连接失败(此时查询ADOConnection.connected ...
- 拉勾网爬取全国python职位并数据分析薪资,工作经验,学历等信息
首先前往拉勾网“爬虫”职位相关页面 确定网页的加载方式是JavaScript加载 通过谷歌浏览器开发者工具分析和寻找网页的真实请求,确定真实数据在position.Ajax开头的链接里,请求方式是PO ...