什么是Java内存模型(JMM)
什么是java内存模型
缓存一致性问题
在现代计算机中,因为CPU的运算速度远大于内存的读写速度,因此为了不让CPU在计算的时候因为实时读取内存数据而影响运算速度,CPU会加入一层缓存,在运算之前缓存内存的数据,CPU运算的时候操作的是缓存里的数据,运算完成后再同步回内存。这样虽然能够加速程序的运行速度,但是却带来了一个问题:缓存一致性问题。每个处理器都有自己的缓存,而它们又共享同一内存,当有多个处理器的操作涉及同一块内存区域的时候,他们的缓存可能会因为运算而导致不一致,在这种情况下,同步回内存的数据以谁的为准呢?为了解决一致性问题,需要各个处理器访问缓存的时候都遵循一些协议,在读写时要根据协议来进行操作,这类协议有MSI、MESI、MOSI、Synapse、Firefly及Dragon Protocol等。
而Java中的线程在执行的时候,为了提高速度,也会把线程中使用到的公共变量缓存到线程本地备份,线程执行时实际操作的是线程本地备份,运算完成后再同步到公共变量。Java这种机制可以看成是硬件缓存之上的一种抽象,在Java实现于特定硬件的时候,就可以把公共变量保存到内存,把线程本地备份保存到CPU缓存从而提升运行速度。Java的这种缓存机制和硬件的缓存机制一样,存在缓存一致性问题。Java的缓存一致性问题怎么解决,参考处理器缓存一致性的解决方案,我们认为应该也需要某种协议。
重排序问题
编译器在编译的时候,允许重排序指令以优化运行速度。CPU在执行指令的时候,为了使处理器内部运算单元能被充分利用,也可以对指令进行乱序执行。
在编译器和CPU进行重排序的时候,要遵循“as-if-serial”原则,也就是要保证程序单线程执行的时候,重排序之后程序的运行结果必须和重排序前程序的运行结果一致。这里注意“as-if-serial”原则只保证单线程的执行结果不变,不保证多线程执行的结果不变。那么如何保证多线程程序的正确运行?显然需要某种协议来限定多线程执行时要满足的规则。
某种协议
要解决缓存一致性问题需要某种协议,要解决重排序问题也需要某种协议。于是java定义了一种协议,一揽子解决了这两个问题,这个协议就是Java内存模型(JMM)。
java内存模型的定义
既然我们知道了JMM是一个协议,那么就让我们快点给出JMM的定义吧。对不起,笔者在这里无法给出JMM的正式定义。而且笔者目前找到的资料里,给出了JMM正式定义的只有java的官方文档JSR133(JSR133是随jdk1.5发布的新的java内存模型,对之前的java内存模型进行了修正,JSR133中定义的内存模型一直沿用到现在(jdk8)),为什么众多讲JMM的博客、文章都不给出JMM的正式定义呢?因为JMM的正式定义太复杂了。我可以展示JMM正式定义的一部分大家来感受一下:

看不懂的同学先别着急,这个定义确实是难懂,JSR133里面也说JMM的正式定义是一个学术定义,笔者也没有看这个正式定义。那么说了半天,原来笔者自己也搞不懂JMM是什么吗?非也非也~虽然JSR133里这个学术定义晦涩难懂,但是JSR133里提供了一个“通俗版”的JMM定义,“通俗版”的定义和正式版略有歧义,不过作为java开发者来说,以"通俗版"定义为准则开发出来的多线程程序,保证是实现正确的。所谓的“通俗版”定义,就是先行发生原则(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(),那么A线程的ThreadB.start()操作happens-before于线程B中的任意操作。
- join()规则:如果线程A执行操作ThreadB.join()并成功返回,那么线程B中的任意操作happens-before于线程A从ThreadB.join()操作成功返回。
先行发生原则规定了在一些情况下多线程程序必须表现出来的执行顺序性,当我们判断一个多线程程序是否能正确执行时,可以根据这些原则来判断。
JSR133中定义的并发关键字的内存语义
为了实现JSR133中定义的新的内存模型,JSR133中增强了volatile和final关键字的语义。下面让我们看一下一些并发关键字的内存语义。
volatile的内存语义
volatile在JSR133中增强了内存语义:
当写一个volatile内存变量的时候,JMM会把该线程对应的本地内存中的共享变量值刷新到主内存。
当读一个volatile变量时,JMM会把该线程对应的本地内存置为无效。线程接下来将从主内存中读取共享变量。
如果把volatile写和读两个步骤综合起来看的话,在读线程B读一个volatile变量后,写线程A在写这个volatile变量之前所有可见的共享变量的值都将立即变得对读线程B可见。
锁的内存语义
当线程释放锁时,JMM会把该线程对应的本地内存中的共享变量刷新到主内存中。
当线程获取锁时,JMM会把该线程中对应的本地内存置为无效。从而使被监视器保护的临界区代码必须从主内存中读取共享变量。
可以看出,锁释放与volatile写有相同的内存语义;锁获取与volatile读有相同的内存语义。
final的内存语义
在构造函数中对一个final域的写入,与随后把这个被构造对象的引用赋值给一个引用变量,这两个操作之间不能重排序。
初次读一个包含final域的对象的引用,与随后初次读这个final域,这两个操作之间不能重排序。
final的内存语义无非是要保证在多线程的情况下,也让final变量确保在初始化后就不能被修改。
参考资料:
《深入理解Java虚拟机 JVM高级特性与最佳实践》第12、13章
《java并发编程的艺术》第3章
《JSR-133 JavaTM Memory Model and Thread Specification》
http://tutorials.jenkov.com/java-concurrency/java-memory-model.html
https://www.cs.umd.edu/~pugh/java/memoryModel/jsr-133-faq.html
什么是Java内存模型(JMM)的更多相关文章
- Java内存模型JMM与可见性
Java内存模型JMM与可见性 标签(空格分隔): java 1 何为JMM JMM:通俗地讲,就是描述Java中各种变量(线程共享变量)的访问规则,以及在JVM中将变量存储到内存和从内存中读取变量这 ...
- 多线程并发之java内存模型JMM
多线程概念的引入是人类又一次有效压寨计算机的体现,而且这也是非常有必要的,因为一般运算过程中涉及到数据的读取,例如从磁盘.其他系统.数据库等,CPU的运算速度与数据读取速度有一个严重的不平衡,期间如果 ...
- Java内存模型JMM 高并发原子性可见性有序性简介 多线程中篇(十)
JVM运行时内存结构回顾 在JVM相关的介绍中,有说到JAVA运行时的内存结构,简单回顾下 整体结构如下图所示,大致分为五大块 而对于方法区中的数据,是属于所有线程共享的数据结构 而对于虚拟机栈中数据 ...
- 全面理解Java内存模型(JMM)及volatile关键字(转载)
关联文章: 深入理解Java类型信息(Class对象)与反射机制 深入理解Java枚举类型(enum) 深入理解Java注解类型(@Annotation) 深入理解Java类加载器(ClassLoad ...
- 全面理解Java内存模型(JMM)及volatile关键字(转)
原文地址:全面理解Java内存模型(JMM)及volatile关键字 关联文章: 深入理解Java类型信息(Class对象)与反射机制 深入理解Java枚举类型(enum) 深入理解Java注解类型( ...
- 对多线程java内存模型JMM
多线程概念的引入体现了人类重新有效压力寨计算机.这是非常有必要的,由于所涉及的读数据的过程中的一般操作,如从磁盘.其他系统.数据库等,CPU计算速度和数据读取速度已经严重失衡.假设印刷过程中一个线程将 ...
- 深入理解Java内存模型JMM与volatile关键字
深入理解Java内存模型JMM与volatile关键字 多核并发缓存架构 Java内存模型 Java线程内存模型跟CPU缓存模型类似,是基于CPU缓存模型来建立的,Java线程内存模型是标准化的,屏蔽 ...
- Java内存模型(JMM)详解
在Java JVM系列文章中有朋友问为什么要JVM,Java虚拟机不是已经帮我们处理好了么?同样,学习Java内存模型也有同样的问题,为什么要学习Java内存模型.它们的答案是一致的:能够让我们更好的 ...
- Java并发编程:Java内存模型JMM
简介 Java内存模型英文叫做(Java Memory Model),简称为JMM.Java虚拟机规范试图定义一种Java内存模型来屏蔽掉各种硬件和系统的内存访问差异,实现平台无关性. CPU和缓存一 ...
随机推荐
- 洛谷 P2324 [SCOI2005]骑士精神 解题报告
P2324 [SCOI2005]骑士精神 题目描述 输入输出格式 输入格式: 第一行有一个正整数T(T<=10),表示一共有N组数据.接下来有T个5×5的矩阵,0表示白色骑士,1表示黑色骑士,* ...
- 关于Mybatis的@Param注解 及 mybatis Mapper中各种传递参数的方法
原文:https://blog.csdn.net/mrqiang9001/article/details/79520436 关于Mybatis的@Param注解 Mybatis 作为一个轻量级的数 ...
- 使用canvas画一个雷达效果图的特效代码
<!doctype html> <html> <head> <meta charset="utf-8"> <title> ...
- LruCache:从网络加载图片缓存实例
OOM异常 堆内存用于存储实例对象,当程序不断创建对象,并且对象都有引用指向,那么垃圾回收机制就不会清理这些对象,当对象多到挤满堆内存的上限后,就产生OOM异常.Android系统为每个应用程序使用的 ...
- 51Nod 1091 线段重叠 | 贪心
Input示例 5 1 5 2 4 2 8 3 7 7 9 Output示例 4 first try: O(n^2):二层循环,减法取最大 后五个time limit exceeded #includ ...
- outer的使用
outer就是一个标签,java语言中根本没有此关键字,因此outer也可以用其它的词来代替 java中的标签就是一个紧跟着英文冒号(:)的标识符.与其他语言不同的是,java中的标签只有放在循环语言 ...
- 【poj2947】高斯消元求解同模方程组【没有AC,存代码】
题意: p start enda1,a2......ap (1<=ai<=n)第一行表示从星期start 到星期end 一共生产了p 件装饰物(工作的天数为end-start+1+7*x, ...
- 【poj1830-开关问题】高斯消元求解异或方程组
第一道高斯消元题目~ 题目:有N个相同的开关,每个开关都与某些开关有着联系,每当你打开或者关闭某个开关的时候,其他的与此开关相关联的开关也会相应地发生变化,即这些相联系的开关的状态如果原来为开就变为关 ...
- 省队集训Day1 过河
[题目大意] 小奇特别喜欢猪,于是他养了$n$只可爱的猪,但这些猪被魔法猪教会了魔法,一不看着某些猪就会自己打起来. 小奇要带着他的猪讨伐战狂,路途中遇到了一条河.小奇找到了一条船,可惜这条船一次只能 ...
- 【vijos】P1448 校门外的树
[题意]两种操作,[L,R]种新的树(不覆盖原来的),或查询[L,R]树的种类数.n<=50000. [算法]树状数组||线段树 [题解]这题可以用主席树实现……不过因为不覆盖原来的,所以有更简 ...