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 进程所管理的内存划分为若干不同的数据区域.这些区域有各自的用途.创建/销毁时间: 一. 线程私有区域 线程私有数据区域生 ...
随机推荐
- let块级作用域
let是es6中新加的作用域,即块级作用域. var申明的变量要么全局,要么函数级,而let允许把变量的作用域限制在块级域中,这里的块级可以是()内,或{}内. 示例: code_1: "u ...
- hdu 2063 过山车 二分匹配(匈牙利算法)
简单题hdu2063 题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=2063 过山车 Time Limit: 1000/1000 MS (Java/Ot ...
- java类中访问属性
package first; public class for_protect { private int age=10; int number = 100; public void show(){ ...
- python基础之上下文管理器
前言 关于计算器运行的上下文的概念,我的理解也不是很深:按我的理解就是程序在运行之前,其所需要的资源,运行环境等都会被序列化,然后加入到CPU的任务队列中,等待调度系统分配时间片执行.下面谈谈pyth ...
- 头像截图上传三种方式之一(一个简单易用的flash插件)(asp.net版本)
flash中有版权声明,不适合商业开发.这是官网地址:http://www.hdfu.net/ 本文参考了http://blog.csdn.net/yafei450225664/article/det ...
- C++ 和Java继承机制的比较
摘要: C++支持类的多继承,而Java采用类的单继承.C++中的继承成分只有类(模板属于带参数的类,结构和联合是特殊的类),Java中除了类还有接口的继承,而且允许接口的多继承,可以间接地实现类多继 ...
- storm的acker机制
一.简介: storm中有一个很重要的特性: 保证发出的每个tuple都会被完整处理.一个tuple被完全处理的意思是: 这个tuple以及由这个tuple所产生的所有的子tuple都被成功处理.如果 ...
- Validate Binary Search Tree——体现二查搜索树思想的一道题
Given a binary tree, determine if it is a valid binary search tree (BST). Assume a BST is defined as ...
- sql server 存储过程解密
Create PROCEDURE [dbo].[sp_windbi$decrypt] () AS /**//* 王成辉翻译整理,转贴请注明出自微软BI开拓者www.windbi.com 调用形式为: ...
- javax.persistence.EntityNotFoundException: Unable to find报错
这类错id 可能是10,可能是27,也可能是其他数字 错误描述: javax.persistence.EntityNotFoundException: Unable to find 某个类 with ...