当我们在谈论JMM(Java memory model)的时候,我们在谈论些什么
前面几篇中,我们谈论了synchronized、final以及voilate的用法和底层实现,都绕不开一个话题-Java内存模型(java memory model,简称JMM)。Java内存模型是保证线程安全的基础,主要描述了程序中全序的同步动作在不同线程访问共享全局变量时所体现的原子性、可见性和有序性上的限制。
1、定义
维基百科定义:The Java memory model describes how threads in the Java programming language interact through memory. Together with the description of single-threaded execution of code, the memory model provides the semantics of the Java programming language
大意是说:Java内存模型描述了多个Java线程如何与内存交互,同单线程执行一样,在多线程场景下内存模型提供了一个合理正确Java编程语意。
JSR133规范由JSR133专家组开发,并首次在Java5.0中实现。规范详细准确的描述了多线程和内存交互语意,成为Java规范的一部分,改进原有Java语意中错误,模凌两可的部分,保证Java跨平台性。
JSR133中关于内存模型的定义如下:
给定一个程序和该程序的一串执行轨迹,内存模型描述了该执行轨迹是否是该程序 的一次合法执行。对于Java,内存模型检查执行轨迹中的每次读操作,然后根据特定规则,检验该读操作观察到的写是否合法。内存模型描述了某个程序的可能行为。JVM实现可以自由地生成想要的代码,只 要该程序所有最终执行产生的结果能通过内存模型进行预测。这为大量的代码转换 提供了充分的自由,包括动作(action)的重排序以及非必要的同步移除。
内存模型的一个高级、非正式的概述显示其是一组规则,规定了一个线程的写操作何时会对另一个线程可见。通俗地说,读操作r通常能看到任何写操作w写入的值,意味着w不是在r之后发生,且w看起来没有被另一个写操作w'覆盖掉(从 r 的角度看)。
在本内存模型规范中使用“读取(read)”这个词时,仅是指读取字段或数组元素的动作(action)。其它操作的语义,如读取数组的长度,执行受检转换以及虚方法 调用,都不会被数据争用直接影响到。JVM 实现有责任确保数据争用不会导致诸如返回错误的数组长度或调用虚方法导致段错误这类不正确的行为。
内存语义决定着程序中每个时刻能读到的值。每个单个线程中的动作(action)必须表现为被该线程的语义所控制,不包括读操作看到的值由内存模型决定的情况。当指的是这种情景时,我们说该程序遵守线程内(intra-thread)语义。
2、JMM近似模型
为了方便理解JMM,相比正式模型精确公式化的定义,JSR133提出了一种近似的模型-Happen-Before内存模型,这个描述是正式定义的必要非充分条件。首先需要解释一下同步动作的定义,同步动作指的是锁的加锁解锁,voilate对象读取,线程动作以及探测线程是否结束等。与同步动作对应的是同步边缘(synchronize-with边缘),同步边缘可以理解为同步动作间不可交叠屏障,包括以下几点:
- 某个管程m上的解锁动作synchronizes-with(同步与)所有后续在m上的锁定动作 (这里的后续是根据同步顺序定义的)。
- 对volatile变量v的写操作synchronizes-with所有后续任意线程对v的读操 作(这里的后续是根据同步顺序定义的)。
- 用于启动一个线程的动作synchronizes-with该新启动线程中的第一个动作。
- 线程T1的最后一个动作synchronizes-with线程T2中任一用于探测T1是否 终止的动作。T2可能通过调用 T1.isAlive()或者在T1上执行一个join动作来达到这个目的。
- 如果线程T1中断了线程T2,T1的中断操作synchronizes-with任意时刻任何其它线程(包括T2)用于确定T2是否被中断的操作。这可以通过抛出一个 InterruptedException或调用Thread.interrupted与Thread.isInterrupted来实现。
- 为每个变量写默认值(0,false或null)的动作synchronizes-with每个线程中的第一个动作。
- 虽然在对象分配之前就为该对象中的变量写入默认值看起来有些奇怪,从概念上看,程序启动创建对象时都带有默认的初始值。因此,任何对象的默认初始化操作 happens-before程序中的任意其它动作(除了写默认值的操作)。
- 调用对象的终结方法时,会隐式的读取该对象的引用。从一个对象的构造器末尾到该引用的读取之间存在一个happens-before边缘。注意,该对象的所有冻结操作happen-before前面那个happens-before边缘的起始点。
下面我们详细说一下这几条限制,第一条说明了管程(synchronized底层实现monitor)的加锁解锁具有同步关系,包括两点,同一个线程可以对已加锁对管程重复加锁,保证解锁次数一样即可,对于已经被加锁管程,线程阻塞并挂起。第二条volatile变量的写操作会在之后其他线程对volatile变量的读操作有即时的体现,一般通过内存一致性协议实现,一般写操作后会将对象的缓存写入内存,并使得其他核的缓存失效,从而读取内存中最新修改的值,这两条的实现在前面的文章中已有较详细的说明。第三条和第四条都是描述线程的启动在线程的第一个动作之前,线程最后一个动作在线程(探询终止???这个不是很理解)终止之前,不得重排序。第五条描述的是线程A对线程B的中断操作同步与线程B的中断检测动作,两个动作互斥发生。第六条第七条说明了,变量的默认值在线程中第一个动作或者其他操作之前,其实有时候编译器会优化变量默认值但赋值,只要该变量还未使用。第八条说明了对象的字段在构造器中的初始化赋值和对象引用返回,final字段保证初始化赋值在对象引用返回之前,而非final字段不保证。
所有的同步动作构成的全局顺序,称之为同步顺序,而synchronize-with边缘与程序顺序构成了Happen-before顺序,也就是Happen-before内存模型。Java内存模型是Happen-before内存模型的子集,因为Happen-before模型很多时候违背了因果关系,最为致命的弱点是“值的凭空出现”。关于Java内存模型的正式规范请参考JSR133第7章内容(我也看的云里雾里--!)
3、总结
总的来说,内存模型这个概念是由Java语言首先提出,在保证java语义完整性健壮性上起到了很大作用,C/C++以及其他语言在锁多线程并发模型上也参考这样的内存模型。理解JMM能让我们更加深刻的正确理解JAVA多线程并发执行中与内存交互的逻辑,从而编写出健壮高效的并发程序。
当我们在谈论JMM(Java memory model)的时候,我们在谈论些什么的更多相关文章
- java学习:JMM(java memory model)、volatile、synchronized、AtomicXXX理解
一.JMM(java memory model)内存模型 从网上淘来二张图: 上面这张图说的是,在多核CPU的系统中,每个核CPU自带高速缓存,然后计算机主板上也有一块内存-称为主内(即:内存条).工 ...
- 浅析java内存模型--JMM(Java Memory Model)
在并发编程中,多个线程之间采取什么机制进行通信(信息交换),什么机制进行数据的同步? 在Java语言中,采用的是共享内存模型来实现多线程之间的信息交换和数据同步的. 线程之间通过共享程序公共的状态,通 ...
- JMM(java Memory Model)到底是什么?
经历过很多面试大部分都会问一句: 你知道Java内存模型么? 然后我就pulapula的说一大堆什么堆呀,栈呀,GC呀什么的,这段时间把JVM虚拟机和多线程编程完整的学习了一遍,发现JMM和堆/栈这 ...
- JAVA内存模型(Java Memory Model ,JMM)
http://blog.csdn.net/hxpjava1/article/details/55189077 JVM有主内存(Main Memory)和工作内存(Working Memory),主内存 ...
- 死磕 java同步系列之JMM(Java Memory Model)
简介 Java内存模型是在硬件内存模型上的更高层的抽象,它屏蔽了各种硬件和操作系统访问的差异性,保证了Java程序在各种平台下对内存的访问都能达到一致的效果. 硬件内存模型 在正式讲解Java的内存模 ...
- 并发研究之Java内存模型(Java Memory Model)
Java内存模型JMM java内存模型定义 上一遍文章我们讲到了CPU缓存一致性以及内存屏障问题.那么Java作为一个跨平台的语言,它的实现要面对不同的底层硬件系统,设计一个中间层模型来屏蔽底层的硬 ...
- Java内存模型(Java Memory Model,JMM)
今天简单聊聊什么叫做 Java 内存模型,不是 JVM 内存结构哦. JMM 是一个语言级别的内存模型,处理器的硬件模型是硬件级别,Java中的内存模型是内存可见性的基本保证.从而为我们 volati ...
- Java 内存模型(Java Memory Model,JMM)
基本概念 JMM 本身是一种抽象的概念并不是真实存在,它描述的是一组规范,通过这组规范定义了程序的访问方式 JMM 同步规定 线程解锁前,必须把共享变量的值刷新回主内存 线程加锁前,必须读取主内存的最 ...
- 并发编程之java内存模型(Java Memory Model ,JMM)
一.图例 0.两个概念 Heap(堆):运行时的数据区,由垃圾回收负责,运行时分配内存(所以慢),对象存放在堆上 如果两个线程,同时调用同一个变量,怎两个线程都拥有,该对象的私有拷贝 (可以看一下,T ...
随机推荐
- [机器学习实战]K-近邻算法
1. K-近邻算法概述(k-Nearest Neighbor,KNN) K-近邻算法采用测量不同的特征值之间的距离方法进行分类.该方法的思路是:如果一个样本在特征空间中的k个最相似(即特征空间中最邻近 ...
- FPGA与MATLAB数据交互高效率验证算法——仿真阶段
之前博文是对基本设计技巧的总结和一些小设计随笔,内容有点杂,缺乏目的性.本来后续计划设计几个小项目,但导师的任务比较紧,所以为了提高效率,后续博客会涉及到很多算法方面的设计与验证的内容,主要关于OFD ...
- MongoDB 更新文档
MongoDB 使用 update() 和 save() 方法来更新集合中的文档.接下来让我们详细来看下两个函数的应用及其区别. update() 方法 update() 方法用于更新已存在的文档.语 ...
- Oracle中如何导出存储过程、函数、包和触发器的定义语句?如何导出表的结构?如何导出索引的创建语句?
Oracle中如何导出存储过程.函数.包和触发器的定义语句?如何导出表的结构?如何导出索引的创建语句? QQ群里有人问:如何导出一个用户下的存储过程? 麦苗答:方法有多种,可以使用DBMS_MET ...
- webstorm 2018.1 激活码 2018.4.8日更新
安装完成后,打开 WebStorm, 在打开的 License Activation 窗口中选择 License server. 在输入框输入网址即可: http://hb5.s.osidea.cc: ...
- Windows Socket的UDP和TCP编程介绍
1:网络中进程之间如何通信 为了实现进程之间通信,首要解决的问题是如何唯一标识一个进程,在本地可以通过进程PID来唯一标识一个进程,但是在网络中则是行不通的,其实TCP/IP协议族已经帮我们解决了这个 ...
- 使用WSUS离线下载补丁并安装在非联网的windows系统中(以Windows Server 2008 r2为例)
首先我失去https://serverfault.com/questions/322938/finding-and-downloading-all-available-win2008-r2-and-w ...
- linux下nginx负载均衡搭建
[一.Nginx能做什么] 1.http服务器.Nginx是一个http服务可以独立提供http服务.可以做网页静态服务器. 2.虚拟主机.可以实现在一台服务器虚拟出多个网站.例如个人网路使用的虚拟主 ...
- [LeetCode] Single Element in a Sorted Array 有序数组中的单独元素
Given a sorted array consisting of only integers where every element appears twice except for one el ...
- HttpClient入门二
通过上一节我们已经可以实现对一个网站源码的抓取. 但是,有一些网站,在爬取的时候会出现如下的错误: HTTP/1.1 503 Service Unavailable <!DOCTYPE html ...