什么叫Java内存模型?

现代计算机通过指令的重排序来提升计算机的性能,而没有限制条件的指令重排序会使得程序的行为不可预测,JMM就是通过一系列的操作规则限制指令重排序的方式使得指令重排序不会破坏JMM提供的可见性,同时JMM通过让JVM在适当的位置插入内存栅栏来屏蔽JMM与底层平台内存模型之间的差异。

背景知识:

*每秒处理事务数:衡量一个服务性能的高低好坏,每秒处理事务数是重要的衡量指标之一

*高速Cache:由于计算机的存储设备与处理器的运算速度有几个数量级的差距,所以现代计算机都不得不加入一层读写速度尽可能的接近处理器运算速度的高速缓存来作为内存和处理器直接的缓冲

*缓存一致性协议:用于处理给高速缓存中数据一致性的问题

*处理器<--->高速缓存<--->缓存一致性协议<--->主内存

*如果存在一个计算任务依赖另外一个计算任务中间结果,那么其顺序性并不能依靠代码的先后来保证,这是数据的依赖性,指令重排优化要遵循数据的依赖性

*Java线程<--->工作内存<--->sava和store操作<--->主内存

ps:放图的目的就是要类比上面两张图!!!

正文:

对比上面两张图,我们把处理器和java线程做类比,高速缓存和工作内存做类比,主内存是同一个,那么我们java中有没有类似缓存一致性协议的协议来处理工作内存和主内存之间的实现细节呢?即一个变量如何从主内存拷贝到工作内存,如何从工作内存同步回到主内存的呢

答案肯定是有的,java内存模型中(和java内存区域中的堆,栈,方法区等不是同一个层次的划分)定义了8种操作来实现主内存和工作内存之间的交互协议,每一种操作都是原子性的!

java内存模型中的8种操作:

1)lock:锁定,作用于主内存变量,把一个变量标识为一条线程独占状态

2)unlock:解锁,作用于主内存变量,把一个处于lock状态的变量释放出来,释放后的变量才可以被其他线程锁定

3)read:读取,作用于主内存的变量,把一个变量从主内存传输到线程工作内存,以便随后的load使用

4)load:载入,作用于工作内存变量,它把read操作从主内存中得到的值放入工作内存的变量副本中

5)use:使用,作用于工作内存变量,把工作内存变量的值传递给执行引擎,每当虚拟机遇到一个需要使用变量的值的字节码指令就会执行这个操作

6)assign:赋值,作用于工作内存变量,把从执行引擎接收到的值赋给工作内存变量,每当虚拟机遇到一个需要给变量赋值的字节码指令就会执行这个操作

7)store:存储,作用于工作内存变量,把一个工作内存变量的值送到主内存中,以便后面的write操作使用

8)write:写入,作用于主内存变量,把store操作从工作内存得到的变量的值放入主内存变量中

以上8个操作都是原子操作!

要从主内存读取一个数据,必定要执行read和load,而且read要在load前面执行,只是要求了执行的顺序,却没有要求一定要连续执行,所以我们可以进行指令重排,那我们这8种操作怎么保证多线程环境下是安全的呢?所以我们java中还存在8种操作的操作规则

操作规则:

1)read在load前,store在write前,都不能单独出现,得成对,出现还得满足顺序

2)工作内存中的变量改变后必须同步到主内存

3)不允许一个线程无原因的(没有发生任何assign操作)把数据从线程的工作内存同步到主内存中

4)新的变量只能在主内存中诞生,不允许在工作内存中使用一个没有被初始化(load和assign)的变量,即对一个变量实施use和store操作之前必须先执行过了assign和load

5)一个变量在同一时刻只允许一个线程对其进行lock,但lock操作可以被同一条线程执行多次,执行多次lock后只有进行相同次数的unlock才能释放变量

6)如果对一个变量执行lock操作,那将会清空工作内存中此变量的值,在执行引擎使用这个变量前,需要重新执行load或assign操作来初始化变量的值

7)如果一个变量没有被lock操作锁定,那么不允许对它进行unlock操作,也不允许去unlock一个被其他线程lock的变量

8)对一个变量执行unlock操作之前,必须把此变量同步回到主内存中(执行store,write)

分析:

这8种操作加上这8种操作规则,虽然可以保证一些内存操作是安全的,但是它实现起来非常的繁琐,不好实现,我们有一种代替这8个规则的方法:先行发生原则,满足先行发生原则则可以保证这些内存在多线程环境下是安全的,如果不满足先行发生原则的话,我们的补救措施就是volatile和synchorized

先行发生原则(判断数据是否存在竞争,线程是否安全的重要依据):

天然的先行发生关系,这些先行发生关系无需同步就已经存在了,满足这些先行发生关系的话,我们就不可以对他们进行随意的指令重排,得设置内存屏障,防止被重排

具体的原则:

1)程序次序规则:在一个线程内,按照程序代码顺序,书写在前面的操作先行发生于书写在后面的操作,准确的说,应该是控制流顺序,而不是程序代码顺序,因为要考虑分支循环等结构

2)管程锁定原则:一个unlock操作先行发生于后面对同一个锁的lock操作,这里必须强调是同一个锁,后面是指时间上的先后顺序

3)volatile变量规则:对一个volatile变量的写操作要先行发生于后面对这个变量的读操作,这里的后面同样是指时间上的先后顺序

4)线程启动规则:Thread对象的start方法先行发生于此线程的每一个动作

5)线程终止原则:线程中的所有操作都优先发生于对此线程的终止检测,我们可以通过Thread.join方法,Thread.isAlive的返回值等手段检测到线程已经终止执行

6)线程中断规则:对线程interrupu方法的调用先行发生于被中断线程的代码检测到中断事件的发生,可以通过Thread.interrupted方法检测到是否有中断发生

7)对象终结规则:一个对象的初始化完成(构造函数执行结束)先行发生于它的finalize方法

8)传递性:如果操作A先行发生于操作B,操作B先行发生于操作A,那么可以得出操作A先行发生于操作C的结论

分析:

两个操作如果不满足先行发生原则,那么这两个操作在并发环境下就是不安全的,需要采用volatile或者synchorized或者lock使得线程安全,如果他们满足先行发生原则,那么这两个操作在多线程环境下肯定是线程安全的

(当然,volatile在java里面的运算的非原子性的,导致volatile变量的运算在并发下也一样是不安全的,但是单个volatile变量的读写具有原子性!!!)

volatile变量规则:

关键字volatile是JVM中最轻量的同步机制,volatile具有两种特性:

*保证变量的可见性:对一个volatile变量的读,总是能看到(任意线程)对这个1volatile变量最后的写入,这个新值对于其他线程来说是立即可见的

*屏蔽指令重排序:指令重排序是编译器和处理器为了高效对程序优化进行的手段,下文有详细分析:

volatile语义并不能保证变量的原子性,对任意单个volatile变量的读写具有原子性,但类似于i++,i--这种复合操作不具有原子性,因为自增运算包括读取i,i+1,重新赋值三个步骤,并不具备原子性

由于volatile只能保证变量的可见性和屏蔽指令重新排序,只有满足下面两条规则时,才能使用volatile来保证并发安全,否则就需要加锁(synchorized,lock,Atomic原子类)来保证并发中的原子性

*运算结果不存在数据依赖,或者只有单一的线程改变变量的值

*变量不需要与其他状态变量共同参与不变约束

因为需要在本地代码中插入许多内存屏蔽指令在屏蔽特定条件下重新排序,volatile变量的写操作比读操作慢一些,但是其性能开销比锁低很多

Java内存模型(和堆栈等不是同一层次的划分)的更多相关文章

  1. 求你了,再问你Java内存模型的时候别再给我讲堆栈方法区了…

    GitHub 4.1k Star 的Java工程师成神之路 ,不来了解一下吗? GitHub 4.1k Star 的Java工程师成神之路 ,真的不来了解一下吗? GitHub 4.1k Star 的 ...

  2. 再问你Java内存模型的时候别再给我讲堆栈方法区

    在介绍Java内存模型之前,先来看一下到底什么是计算机内存模型,然后再来看Java内存模型在计算机内存模型的基础上做了哪些事情.要说计算机的内存模型,就要说一下一段古老的历史,看一下为什么要有内存模型 ...

  3. java内存模型及分块

    转自:http://www.cnblogs.com/BangQ/p/4045954.html 1.JMM简介 2.堆和栈 3.本机内存 4.防止内存泄漏   1.JMM简介   i.内存模型概述 Ja ...

  4. java内存模型与线程(转) good

    java内存模型与线程 参考 http://baike.baidu.com/view/8657411.htm http://developer.51cto.com/art/201309/410971_ ...

  5. JVM-7.Java内存模型与高效并发

    更多内容参见<并发与同步>系列 一.引子 二.JMM 三.Java中的线程 四.线程安全 五.锁优化       一.引子 运算能力 摩尔定律:晶体管数量,代表的CPU的频率 Amdahl ...

  6. Java内存模型JMM 高并发原子性可见性有序性简介 多线程中篇(十)

    JVM运行时内存结构回顾 在JVM相关的介绍中,有说到JAVA运行时的内存结构,简单回顾下 整体结构如下图所示,大致分为五大块 而对于方法区中的数据,是属于所有线程共享的数据结构 而对于虚拟机栈中数据 ...

  7. Java 内存模型和硬件内存架构笔记

    前言 可跟<主存存取和磁盘存取原理笔记>串着看 https://blog.csdn.net/suifeng3051/article/details/52611310 杂技 Java 内存模 ...

  8. Java内存模型与共享变量可见性

    此文已由作者赵计刚授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 注:本文主要参考自<深入理解Java虚拟机(第二版)>和<深入理解Java内存模型> ...

  9. Java 内存模型 ,一篇就够了!

    Java 虚拟机   我们都知道 Java 语言的可以跨平台的,这其中的核心是因为存在 Java 虚拟机这个玩意.虚拟机,顾名思义就是虚拟的机器,这不是真实存在的硬件,但是却可以和不同的底层平台进行交 ...

随机推荐

  1. POI 读取 excel

    xls 和 xlsx 后缀是因为 world excel 版本不一致,需要区别对待 依赖 <dependency> <groupId>org.apache.poi</gr ...

  2. RabbitMQ 环境搭建

    安装基础环境 yum install net-tools yum install yum yum install gcc glibc-devel make ncurses-devel openssl- ...

  3. Mysql 子查询

    一个 SELECT 语句中包含另一个或多个 SELECT 语句就是子查询 WHERE 后: 把 SELECT 查询出来的结果当做条件 # 查询和李四同性别的人 SELECT * FROM studen ...

  4. [随时更新][Android]小问题记录

    此文随时更新,旨在记录平时遇到的不值得单独写博客记录的细节问题,当然如果问题有拓展将会另外写博客. 原文地址请保留http://www.cnblogs.com/rossoneri/p/4040314. ...

  5. matlab练习程序(点集配准的SVD法)

    上一篇博客中我们使用了四元数法计算ICP. 本篇我们使用SVD计算ICP. 下面是<视觉slam十四讲>中的计算方法: 计算步骤如下: 我们看到,只要求出了两组点之间的旋转,平移是非常容易 ...

  6. linux 下svn操作

    * 前言: linux下的svn相比于gitlab,配置要求第一点:gitlab需要4G的内存,如果使用swap+内存的替代方案,理论上是可行的,但是实际操作中各种坑:     所以,由于条件限制,使 ...

  7. C#-运算符(四)

    算术运算符 +:两个操作数相加,例:2+3得5 -:第一个操作数减去第二个操作数 例:5-3得2 *:两个操作数相乘,例:2*3得6 /:分子除以分母,例:5/2得2 %:取模运算符,整除后的余数,例 ...

  8. MySQL【Delete误操作】数据恢复【转】

    前言:      操作数据库时候难免会因为“大意”而误操作,需要快速恢复的话通过备份来恢复是不太可能的,因为需要还原和binlog差来恢复,等不了,很费时.这里先说明下因为Delete 操作的恢复方法 ...

  9. c/c++ lambda 表达式 介绍

    lambda 表达式 介绍 问题:假设有个需求是,在vector<string>找出所有长度大于等于4的元素.标准库find_if函数的第三参数是函数指针,但是这个函数指针指向的函数只能接 ...

  10. c/c++柔性数组成员

    柔性数组成员 定义和声明分离 #include <stdio.h> //只是告诉编译器,当编译到使用到这个函数的的代码时,虽然还没有找到函数定义的实体,但是也让它编译不出错误. exter ...