Java并发(二):Java内存模型
一、硬件内存架构
一个现代计算机通常由两个或者多个CPU。其中一些CPU还有多核。每个CPU在某一时刻运行一个线程是没有问题的。如果你的Java程序是多线程的,在你的Java程序中每个CPU上一个线程可能同时(并发)执行。
当一个CPU需要读取主存时,它会将主存的部分读到CPU缓存中。它甚至可能将缓存中的部分内容读到它的内部寄存器中,然后在寄存器中执行操作。
当CPU需要将结果写回到主存中去时,它会将内部寄存器的值刷新到缓存中,然后在某个时间点将值刷新回主存。
二、并发编程的问题
并发编程,为了保证数据的安全,需要满足以下三个特性:
原子性:在一个操作中就是cpu不可以在中途暂停然后再调度,既不被中断操作,要不执行完成,要不就不执行。(处理器优化)
原子性问题:线程在执行一个读改写
操作时,在执行完读改
之后,时间片耗完,就会被要求放弃CPU,并等待重新调度。此时另一个线程对同一个变量执行读改写操作就会出现问题。这种情况下,读改写
就不是一个原子操作。
i = 0; // 基本数据类型的变量和赋值操作都是原子性操作
j = i ; // 包含了两个操作:读取i,将i值赋值给j
i++; // 包含了三个操作:读取i值、i + 1 、将+1结果赋值给i
i = j + 1; // 包含了三个操作:读取j值、j + 1 、将+1结果赋值给i
在单线程环境下我们可以认为整个步骤都是原子性操作。但是在多线程环境下则不同,Java只保证了基本数据类型的变量和赋值操作才是原子性的。要想在多线程环境下保证原子性,则可以通过锁、synchronized来确保。
可见性:当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。(缓存一致性问题)
有序性:程序执行的顺序按照代码的先后顺序执行。(指令重排)
内存模型通过限制处理器优化和使用内存屏障,来保证共享内存的正确性(可见性、有序性、原子性)。
Java内存模型(Java Memory Model ,JMM)就是一种符合内存模型规范的,屏蔽了各种硬件和操作系统的访问差异的,保证了Java程序在各种平台下对内存的访问都能保证效果一致的机制及规范。
JMM还通过volatile
、synchronized
、final
、concurren
包等实现原子性、有序性、可见性。
三、Java内存模型(JMM)
共享变量:堆内存在线程之间共享,存储在堆内存中所有实例域、静态域和数组元素共享变量
(局部变量,方法定义参数、异常处理器参数不会在线程之间共享,不会有内存可见性问题,不受内存模型的影响)
JMM定义了线程和主内存之间的抽象关系:
1)线程之间的共享变量存储在主内存(main memory)中
2)每个线程都有一个私有的本地内存(local memory),本地内存中存储了该线程用以读/写共享变量的副本
3)本地内存是JMM的一个抽象概念,并不真实存在。它涵盖了缓存,写缓冲区,寄存器以及其他的硬件和编译器优化
线程A与线程B通信:
1)线程A把本地内存A中更新过的共享变量刷新到主内存中去
2)线程B到主内存中去读取线程A之前已更新过的共享变量
JMM通过控制主内存与每个线程的本地内存之间的交互,提供内存可见性保证
JMM的设计
1)常见的处理器内存模型比JMM要弱,java编译器在生成字节码时,会在执行指令序列的适当位置插入内存屏障来限制处理器的重排序。
2)由于各种处理器内存模型的强弱并不相同,为了在不同的处理器平台向程序员展示一个一致的内存模型,JMM在不同的处理器中需要插入的内存屏障的数量和种类也不相同。
程序员希望:强内存模型编程,易于理解,易于编程
编译器和处理器希望:弱内存模型,内存模型对它们的束缚越少越好,以提高性能
JMM时的核心目标就是找到一个好的平衡点:一方面要为程序员提供足够强的内存可见性保证;另一方面,对编译器和处理器的限制要尽可能的放松。
JMM把happens- before要求禁止的重排序分为了下面两类:
1)会改变程序执行结果的重排序,JMM要求编译器和处理器必须禁止这种重排序。
2)不会改变程序执行结果的重排序,JMM对编译器和处理器不作要求(JMM允许这种重排序)。
只要不改变程序的执行结果(指的是单线程程序和正确同步的多线程程序),编译器和处理器怎么优化都行。
比如,如果编译器经过细致的分析后,认定一个锁只会被单个线程访问,那么这个锁可以被消除。
再比如,如果编译器经过细致的分析后,认定一个volatile变量仅仅只会被单个线程访问,那么编译器可以把这个volatile变量当作一个普通变量来对待。
这些优化既不会改变程序的执行结果,又能提高程序的执行效率。
四、顺序一致性内存模型
顺序一致性内存模型是一个被计算机科学家理想化了的理论参考模型,它为程序员提供了极强的内存可见性保证(JMM没有顺序一致性内存模型保证)
特性:
- 一个线程中的所有操作必须按照程序的顺序来执行。
- (不管程序是否同步)所有线程都只能看到一个单一的操作执行顺序。在顺序一致性内存模型中,每个操作都必须原子执行且立刻对所有线程可见。
视图信息:
1.顺序一致性模型有一个单一的全局内存
2.在任意时间点最多只能有一个线程可以连接到内存
3.每一个线程必须按程序的顺序来执行内存读/写操作
举例:
线程A:A1->A2->A3 线程B:B1->B2->B3 并发执行
正确同步:
两个线程没有做同步:
可以看出:
1.每个线程内部执行顺序 都是按照程序的顺序来执行
2.所有线程都只能看到一个一致的整体执行顺序(原因:顺序一致性内存模型中的每个操作必须立即对任意线程可见)
顺序一致性模型与JMM区别:
顺序一致性模型保证单线程内的操作会按程序的顺序执行,JMM不保证单线程内的操作会按程序的顺序执行(遵守as-if-serial语义)
顺序一致性模型保证所有线程只能看到一致的操作执行顺序,而JMM不保证所有线程能看到一致的操作执行顺序
JMM在具体实现上的基本方针:在不改变(正确同步的)程序执行结果的前提下,尽可能的为编译器和处理器的优化打开方便之门。
正确同步,JMM保证程序的执行结果将与该程序在顺序一致性模型中的执行结果相同(但不保证执行顺序)
假设A线程执行writer()方法后,B线程执行reader()方法
五、处理器内存模型
如果完全按照顺序一致性模型来实现,那么很多的处理器和编译器优化都要被禁止,这对执行性能将会有很大的影响。
根据对不同类型读/写操作组合的执行顺序的放松,可以把常见处理器的内存模型划分为下面几种类型:
- 放松程序中写-读操作的顺序,由此产生了total store ordering内存模型(简称为TSO)。
- 在前面1的基础上,继续放松程序中写-写操作的顺序,由此产生了partial store order 内存模型(简称为PSO)。
- 在前面1和2的基础上,继续放松程序中读-写和读-读操作的顺序,由此产生了relaxed memory order内存模型(简称为RMO)和PowerPC内存模型。
注意,这里处理器对读/写操作的放松,是以两个操作之间不存在数据依赖性为前提的(因为处理器要遵守as-if-serial语义,处理器不会对存在数据依赖性的两个内存操作做重排序)。
从上到下,模型由强变弱。越是追求性能的处理器,内存模型设计的会越弱。因为这些处理器希望内存模型对它们的束缚越少越好,这样它们就可以做尽可能多的优化来提高性能。
JMM,处理器内存模型,顺序一致性内存模型之间的关系
JMM是一个语言级的内存模型,处理器内存模型是硬件级的内存模型,顺序一致性内存模型是一个理论参考模型。
语言内存模型,处理器内存模型和顺序一致性内存模型的强弱对比示意图:
内存模型越强,越容易保证内存可见性,易编程性就越好。但是重排序就会越少,执行效率就越低。
重排序 :Java并发(三):重排序
happens-before:Java并发(四):happens-before
volatile:Java并发(六):volatile的实现原理
Final:Java并发(十九):final实现原理
参考资料:
Java并发(二):Java内存模型的更多相关文章
- Java并发编程、内存模型与Volatile
http://www.importnew.com/24082.html volatile关键字 http://www.importnew.com/16142.html ConcurrentHash ...
- Java并发编程-Java内存模型
JVM内存结构与Java内存模型经常会混淆在一起,本文将对Java内存模型进行详细说明,并解释Java内存模型在线程通信方面起到的作用. 我们常说的JVM内存模式指的是JVM的内存分区:而Java内存 ...
- java中JVM虚拟机内存模型详细说明
java中JVM虚拟机内存模型详细说明 2012-12-12 18:36:03| 分类: JAVA | 标签:java jvm 堆内存 虚拟机 |举报|字号 订阅 JVM的内部结构 ...
- Java虚拟机学习 - 体系结构 内存模型
一:Java技术体系模块图 二:JVM内存区域模型 1.方法区 也称"永久代” .“非堆”, 它用于存储虚拟机加载的类信息.常量.静态变量.是各个线程共享的内存区域.默认最小值为16MB,最 ...
- Java虚拟机学习 - 体系结构 内存模型(1)
一:Java技术体系模块图 二:JVM内存区域模型 1.方法区 也称"永久代" ."非堆", 它用于存储虚拟机加载的类信息.常量.静态变量.是各个线程共享的内 ...
- Java虚拟机学习 - 体系结构 内存模型(转载)
一:Java技术体系模块图 二:JVM内存区域模型 1.方法区 也称"永久代” .“非堆”, 它用于存储虚拟机加载的类信息.常量.静态变量.是各个线程共享的内存区域.默认最小值为16MB, ...
- 【java虚拟机】jvm内存模型
作者:pengjunlee原文链接:https://blog.csdn.net/pengjunlee/article/details/71909239 目录 一.运行时数据区域 1.程序计数器 2.J ...
- Java:JVM的内存模型
JVM内存模型 JVM内存模型可以分为两个部分,如下图所示,堆和方法区是所有线程共有的,而虚拟机栈,本地方法栈和程序计数器则是线程私有的. 1. 堆(Heap) 堆内存是所有线程共有的,可以分为两 ...
- 深入理解Java虚拟机(一)——JVM内存模型
文章目录 程序计数器 定义 作用 特点 Java虚拟机栈 定义 特点 本地方法栈 定义 Java堆 定义 特点 方法区 定义 特点 运行常量池 直接内存 总结 Java虚拟机的内存空间分为五个部分: ...
- JVM 系列(二)内存模型
02 JVM 系列(二)内存模型 一.JVM 内存区域 JVM 会将 Java 进程所管理的内存划分为若干不同的数据区域.这些区域有各自的用途.创建/销毁时间: 一. 线程私有区域 线程私有数据区域生 ...
随机推荐
- LOW逼三人组(三)----插入排序
插入排序思路 插入排序算法: import random # 随机模块 import time def cal_time(func): # 装饰器 ,用来检测算法所执行的时间 def wrapper( ...
- three.js为何如此奇妙
WebGL是在浏览器中实现三维效果的一套规范,而最初使用WebGL原生的API来写3D程序是一件非常痛苦的事情,在辛苦的付出下WebGL开源框架出现了,其中three.js就是非常优秀的一个,它掩盖了 ...
- XGBoost与LightGBM对比分析(转)
尊重原创 来源: https://blog.csdn.net/a790209714/article/details/78086867 XGBoost的四大改进: ①改进残差函数 不用Gini作为残 ...
- Perl6多线程3: Promise start / in / await
创建一个Promise 并自动运行: my $p = Promise.start({say 'Hello, Promise!'}); 如果把代码改成如下, 我们会发现什么也没打印: ;say 'Hel ...
- ARP投毒攻击
原理:通过分别伪装成客户机和服务器IP,将自己的MAC地址绑定在IP上,ARP错误的将IP解析为中间人MAC地址,从而来欺骗服务器网关和客户机,使信息必须通过客户机.
- 搭建linux+nginx+mysql+php环境
yum install -y gcc gcc-c++ make zlib zlib-devel pcre pcre-devel libjpeg libjpeg-devel libpng libpn ...
- docker安装总结 linux红帽系列
由于Docker限制分为两个版本CE和EE,所以之前yum里面的docker是老版本,需要先进行卸载,现在的包名叫做docker-ce yum remove docker docker-common ...
- Android Studio 找不到EventBus/ButterKnife等第三方包解决方案
废话不多说,有图有真相 Q·:可以正常Build,debug就是看着不舒服,代码提示也出不来. 解决方案: 1. invalidate and restart (没用继续第二步) 2. 修改gradl ...
- 大型网站的 HTTPS 实践(二)——HTTPS 对性能的影响(转)
原文链接:http://op.baidu.com/2015/04/https-s01a02/ 1 前言 HTTPS 在保护用户隐私,防止流量劫持方面发挥着非常关键的作用,但与此同时,HTTPS 也会降 ...
- 学习笔记(二) 瓜娃(guava)的API快速熟悉使用
1,大纲 让我们来熟悉瓜娃,并体验下它的一些API,分成如下几个部分: Introduction Guava Collection API Guava Basic Utilities IO API C ...