Java并发-Java内存模型(JMM)
先来说说什么是内存模型吧
在硬件中,由于CPU的速度高于内存,所以对于数据读写来说会出现瓶颈,无法充分利用CPU的速度,因此在二者之间加入了一个缓冲设备,高速缓冲寄存器,通过它来实现内存与CPU的数据交互。我们现在的计算机都是多CPU多核的,而每个CPU都需要配备一个寄存器,那么问题来了,如果一个CPU对数据进行修改写入了寄存器但没及时更新到主存,另一个CPU也对其进行了修改,便会发生数据错误,最终得到的结果并非我们想要的。
如何解决这一问题呢?缓存一致性!我们需要一个缓存一致性模型来规范化我们的内存读写和寄存器读写,这样才能保证数据一致性。
因此:内存模型可以理解为在特定的操作协议下,对特定的内存或高速缓存进行读写访问的过程抽象。不同架构的CPU 有不同的内存模型。
那么什么是Java内存模型?
我们都知道Java的口号是write once, run anywhere。JMM便是实现这句话的最重要一步!
JVM通过定义一种Java内存模型来屏蔽掉各种硬件和操作系统的内存访问差异,以实现让Java程序在各种平台下都能达到一致的内存访问效果
也就是说,JMM规范了线程的工作内存和主存之间的读写操作。Java 内存模型定义了两个重要的东西,1.主内存,2.工作内存。每个线程的工作内存都是独立的,线程操作数据只能在工作内存中计算,然后刷入到主存。这是 Java 内存模型定义的线程基本工作方式
JMM规范了三种重要的特征,这也是Java并发的基础:原子性,可见性,有序性。
原子性
这里的原子性和事务很相似,对于一个操作是不可以打断的,不可分割的,如果这个操作没有执行完,其他线程不可以干扰。
Java中的基本数据类型的操作大致可以理解为原子操作(32位下的long和double操作除外)。因为 java 虚拟机规范中,对 long 和 double 的操作没有强制定义要原子性的,但是强烈建议使用原子性的。因此,大部分商用的虚拟机基本都实现了原子性。
JMM定义了8中原子性操作:
- lock(锁定):作用于主内存,它把一个变量标记为一条线程独占状态;
- read(读取):作用于主内存,它把变量值从主内存传送到线程的工作内存中,以便随后的load动作使用;
- load(载入):作用于工作内存,它把read操作的值放入工作内存中的变量副本中;
- use(使用):作用于工作内存,它把工作内存中的值传递给执行引擎,每当虚拟机遇到一个需要使用这个变量的指令时候,将会执行这个动作;
- assign(赋值):作用于工作内存,它把从执行引擎获取的值赋值给工作内存中的变量,每当虚拟机遇到一个给变量赋值的指令时候,执行该操作;
- store(存储):作用于工作内存,它把工作内存中的一个变量传送给主内存中,以备随后的write操作使用;
- write(写入):作用于主内存,它把store传送值放到主内存中的变量中。
- unlock(解锁):作用于主内存,它将一个处于锁定状态的变量释放出来,释放后的变量才能够被其他线程锁定;
Java内存模型还规定了执行上述8种基本操作时必须满足如下规则:
1、不允许read和load、store和write操作之一单独出现(即不允许一个变量从主存读取了但是工作内存不接受,或者从工作内存发起会写了但是主存不接受的情况),以上两个操作必须按顺序执行,但没有保证必须连续执行,也就是说,read与load之间、store与write之间是可插入其他指令的。
2、不允许一个线程丢弃它的最近的assign操作,即变量在工作内存中改变了之后必须把该变化同步回主内存。
3、不允许一个线程无原因地(没有发生过任何assign操作)把数据从线程的工作内存同步回主内存中。
4、一个新的变量只能从主内存中“诞生”,不允许在工作内存中直接使用一个未被初始化(load或assign)的变量,换句话说就是对一个变量实施use和store操作之前,必须先执行过了assign和load操作。
5、一个变量在同一个时刻只允许一条线程对其执行lock操作,但lock操作可以被同一个条线程重复执行多次,多次执行lock后,只有执行相同次数的unlock操作,变量才会被解锁。
6、如果对一个变量执行lock操作,将会清空工作内存中此变量的值,在执行引擎使用这个变量前,需要重新执行load或assign操作初始化变量的值。
7、如果一个变量实现没有被lock操作锁定,则不允许对它执行unlock操作,也不允许去unlock一个被其他线程锁定的变量。
8、对一个变量执行unlock操作之前,必须先把此变量同步回主内存(执行store和write操作)
看到一张图很好的描述了这几个关键词的作用:

这些操作JVM是不开放给程序员的,对于编程来说,我们只能使用monitorenter和monitorexit两个字节码指令,也就是 synchronized 关键字,因此在 synchronized 块之间的操作都是原子性的(加锁之后同时只能有一个线程通执行代码块,当然具有原子性了)。
可见性
可见性是指一个线程对一个共享变量做出了修改,其余线程立刻可以得知这个修改。volatile,synchronized,final均可提供可见性保证
volatile是通过在指令后加上lock指令并与数字0求和,从而立刻将值写回主存,并令其余缓存行失效;
对于synchronizedJMM做了两条规定:
1)线程解锁前,必须把共享变量的最新值刷新到主内存中
2)线程加锁时,将清空工作内存中共享变量的值,从而使用共享变量时需要从主内存中重新获取最新的值
(注意:加锁与解锁需要是同一把锁)
有序性
CPU、JVM都会对指令重排序,从而达到优化程序的目的,这种指令顺序调整在单线程里是感知不到的,不过在多线程程序中,这种调整会导致一些问题。JMM通过内存屏障来保证不会对一些必要的操作重排序
我们主要通过两个关键字来保证多个线程之间操作的有序性,volatile关键字本身就包含了禁止重排序的语义,而 synchronized 则是由 “一个变量同一时刻只允许一条线程对其进行加锁操作”这个规则获得的
人为控制有序性意味着JIT不能优化代码,因此要谨慎使用
总之:volatile 保证了可见性和有序性,synchronized 则3个特性都保证了,不过要根据自己业务的并发特性来判断synchronized的性能所带来的问题。
他们的功能有交集,什么时候用 volatile?
运算结果不依赖变量的当前值,或者能够确保只有单一的线程修改变量的值
JMM的HappenBefore原则
刚才说到有序性可以通过 volatile 和 synchronized 来实现,但是我们不可能所有的代码都靠这两个关键字。实际上,Java 语言已对重排序或者说有序性做了规定,这些规定在虚拟机优化的时候是不能违背的。
1. 程序次序原则:一个线程内,按照程序代码顺序,书写在前面的操作先发生于书写在后面的操作。
2. volatile 规则:volatile 变量的写,先发生于读,这保证了 volatile 变量的可见性。
3. 锁规则:解锁(unlock) 必然发生在随后的加锁(lock)前。
4. 传递性:A先于B,B先于C,那么A必然先于C。
5. 线程的 start 方法先于他的每一个动作。
6. 线程的所有操作先于线程的终结。
7. 线程的中断(interrupt())先于被中断的代码。
8. 对象的构造函数,结束先于 finalize 方法
Java并发-Java内存模型(JMM)的更多相关文章
- Java并发之内存模型(JMM)浅析
背景 学习Java并发编程,JMM是绕不过的槛.在Java规范里面指出了JMM是一个比较开拓性的尝试,是一种试图定义一个一致的.跨平台的内存模型.JMM的最初目的,就是为了能够支多线程程序设计的,每个 ...
- 【死磕Java并发】-----内存模型之happens-before
在上篇博客([死磕Java并发]-----深入分析volatile的实现原理)LZ提到过由于存在线程本地内存和主内存的原因,再加上重排序,会导致多线程环境下存在可见性的问题.那么我们正确使用同步.锁的 ...
- Java内存模型JMM 高并发原子性可见性有序性简介 多线程中篇(十)
JVM运行时内存结构回顾 在JVM相关的介绍中,有说到JAVA运行时的内存结构,简单回顾下 整体结构如下图所示,大致分为五大块 而对于方法区中的数据,是属于所有线程共享的数据结构 而对于虚拟机栈中数据 ...
- Java并发编程:Java内存模型JMM
简介 Java内存模型英文叫做(Java Memory Model),简称为JMM.Java虚拟机规范试图定义一种Java内存模型来屏蔽掉各种硬件和系统的内存访问差异,实现平台无关性. CPU和缓存一 ...
- Java内存模型JMM与可见性
Java内存模型JMM与可见性 标签(空格分隔): java 1 何为JMM JMM:通俗地讲,就是描述Java中各种变量(线程共享变量)的访问规则,以及在JVM中将变量存储到内存和从内存中读取变量这 ...
- 多线程并发之java内存模型JMM
多线程概念的引入是人类又一次有效压寨计算机的体现,而且这也是非常有必要的,因为一般运算过程中涉及到数据的读取,例如从磁盘.其他系统.数据库等,CPU的运算速度与数据读取速度有一个严重的不平衡,期间如果 ...
- 全面理解Java内存模型(JMM)及volatile关键字(转载)
关联文章: 深入理解Java类型信息(Class对象)与反射机制 深入理解Java枚举类型(enum) 深入理解Java注解类型(@Annotation) 深入理解Java类加载器(ClassLoad ...
- 全面理解Java内存模型(JMM)及volatile关键字(转)
原文地址:全面理解Java内存模型(JMM)及volatile关键字 关联文章: 深入理解Java类型信息(Class对象)与反射机制 深入理解Java枚举类型(enum) 深入理解Java注解类型( ...
- 什么是Java内存模型(JMM)
什么是java内存模型 缓存一致性问题 在现代计算机中,因为CPU的运算速度远大于内存的读写速度,因此为了不让CPU在计算的时候因为实时读取内存数据而影响运算速度,CPU会加入一层缓存,在运算之前缓存 ...
- 对多线程java内存模型JMM
多线程概念的引入体现了人类重新有效压力寨计算机.这是非常有必要的,由于所涉及的读数据的过程中的一般操作,如从磁盘.其他系统.数据库等,CPU计算速度和数据读取速度已经严重失衡.假设印刷过程中一个线程将 ...
随机推荐
- supersockets服务器配置热更新
Keywords: 配置,热更新 此功能能够允许你在不重启服务器的前提下更新服务器实例的配置. (仅限1.6.5及其以上版本)
- vue+element-ui 字体自适应不同屏幕
项目背景:屏幕自适应问题,当在不同分辨率的屏幕上显示页面时,页面的字体需要根据屏幕大小来自适应,想到使用rem作为字体的单位 vue-cli脚手架下的index.html中写入以下js脚本 <s ...
- VisualStudio 使用多个环境进行调试
在 VisualStudio 2017 支持使用 launchSettings.json 文件定义多个不同的环境进行调试 先给大家一张图看一下效果 可以看到原来的是启动的按钮,现在被我修改为 lind ...
- npm install 报错(npm ERR! errno -4048,Error: EPERM: operation not permitted)
问题现象 原因 1.初次看报错日志内容,定义权限为问题,后来查资料才知道是缓存问题. 解决方法 1.简单直接 直接删除 npmrc文件 tips: 不是nodejs安装目录npm模块下的那个npmrc ...
- Python--day38--JoinableQueue解决生产者消费者模型
############################# # 在消费者这一端: #每次获取一个数据 #处理一个数据 #发送一个记号:标志一个数据被处理成功 #在生产者这一端: #每一次生成一个数据 ...
- Java语言中的正则表达式
正则表达式是什么? 正则表达式是一种强大而灵活的文本处理工具.初学正则表达式时,其语法是一个难点,但它确实是一种简洁.动态的语言.正则表达式提供了一种完全通用的方式,能够解决各种字符串处理相关的问题: ...
- win10 uwp 使用 AppCenter 自动构建
微软在今年7月上线 appcenter.ms 这个网站,通过 App Center 可以自动对数千种设备进行适配测试.快速将应用发送给测试者或者直接发布到应用商店.做到开发的构建和快速测试,产品的遥测 ...
- H3C IEEE EUI-64格式
- zookeeper(1)-概述
ZooKeeper概述 ZooKeeper 是一个分布式的,开放源码的分布式应用程序协调服务,是 Google 的 Chubby 一个开源的实现.它提供了简单原始的功能,分布式应用可以基于它实现更高级 ...
- Linux 内核USB 驱动
通用串行总线(USB)是一个在主机和许多外设之间的连接. 最初它被创建来替代许多慢速和不同的总线- 并口, 串口, 和键盘连接--有一个单个的所有设备都可以连接的总线类型.[45] USB 已经成长超 ...