Java内存模型与线程(一)
Java内存模型与线程
TPS:衡量一个服务性能的标准,每秒事务处理的总数,表示一秒内服务端平均能够响应的总数,TPS又和并发能力密切相关。
在聊JMM(Java内存模型)之前,先说一下Java为什么要定义出JMM,那就要从Java内存模型的作用谈起,Java内存模型是用来屏蔽各种硬件和操作系统的内存访问差异,以实现让Java程序在各种平台下都能达到一致的内存访问效果。在此之前,C++/C直接使用物理硬件和操作系统的内存模型,因此,会由于平台或者操作系统的不同,有可能导致在一个平台上内存访问正常但是在另一台并发访问内存却会出错。
在JDK1.5之后,Java内存模型已经成熟和完善起来了。
主内存和工作内存
JMM主要的目标是定义程序中各个变量的访问规则,这里的变量包含实例对象,静态变量,数组但不包含方法上的参数和局部变量,因为后者是线程私有的,不存在共享问题。
主内存:JMM规定了所有的变量存在主内存中,他这个名字和操作系统中的主存一样但是意义不一样,此处指的是虚拟机内存的一部分,两者的对比图在下文。
工作内存:线程的工作内存保存了被线程使用到的变量的主内存副本拷贝,线程对变量的所有操作(读取、赋值等)都只能在工作内存中操作,不能直接在主内存操作变量。不同线程之间无法直接访问工作内存中的变量,变量的传递需要靠主内存完成。
线程、主内存和工作内存之间的关系

处理器、高速缓存、主内存之间的交互关系

内存之间的交互
对于工作内存的变量同步到主内存,主内存的变量的拷贝写入到工作内存的实现细节,全部采用以下8中原子操作来完成。
- lock(锁定):作用于主内存的变量,他把一个变量标识为一条线程独占的状态。
- unlock(解锁):作用于主内存的变量,他把处于锁定的变量释放出来,释放后的变量才可以被其他线程锁定。
- read(读取):作用于主内存的变量,将一个变量的值从主内存中传输到线程的工作内存中。以便以后的load操作。
- load(载入):作用于工作内存的变量,他把read操作从主内存得到的变量值放入工作内存的变量副本中。
- use(使用):作用于工作内存的变量,他把工作内存中一个变量的值传递给执行引擎,每当虚拟机遇到一个需要使用到变量的字节码指令时就会使用到这个变量。
- assign(赋值):作用于工作内存的变量,他把一个执行引擎接受到的值赋值给工作内存的变量,每当虚拟机遇到一个给变量赋值的字节码指令的时候就执行这个操作。
- store(存储):作用于工作内存的变量,他把工作内存的变量的值传递给主内存中,一边随后的write操作。
- write(写入):作用于主内存的变量,他把store操作从工作内存中得到的变量的值放入主内存的变量中。
如果要把变量从主内存复制到工作内存,就要顺序执行read和load操作,如果要把变量从工作内存同步到主内存就要顺序执行 store和write操作。注意顺序执行的含义是,read要在load之前,但是中间可以进行其他的操作例如read a; read b; load b ;load a; 同样store和write也是一样。
以上8中操作必须满足以下8个规则
1、不允许read和load、store和write操作之一单独出现。
2、不允许一个线程丢弃他最近的assign操作,也就是变量在工作内存中改变了之后必须把该变化同步回主内存。
3、不允许一个线程无原因(没有发生任何的assign操作)的将数据从工作内存同步回主内存。
4、不允许新的变量诞生在工作内存换句话说就是store要在load之前,assign要在use之前。
5、一个变量在同一时刻只允许一条线程对其进行lock操作,但是lock可以被同一线程执行多次,多次执行lock后需要执行相同次数的unlock,变量才能被解锁。
6、如果对一个变量执行lock,那将会清除工作内存中此变量的值,在执行引擎使用这个变量前,需要重新执行load或者assign操作初始化变量的值。
7、如果一个变量事先没有被lock,那就不允许对他执行unlock,还有不允许unlock一个被其他线程锁定住的变量。
8、在对一个变量执行unlock之前,必须先把这个变量同步回主内存中(执行store和write)
对于volatile变量的特殊规则
Java内存模型对于volatile专门定义了一些特殊的访问规则。在没有volatile之前,一个线程A修改一个变量的值,然后向主内存同步,另一个线程B才能看到这个变量新的值,因此我们可以说这个变量对于线程B可见。
volatile可以保证可见性但是无法保证原子性,所以在并发条件下被volatile修饰的变量也是线程不安全的。
由于volatile变量只能保证可见性,在不符合以下两个场景中,我们仍要通过加锁来保证原子性。
- 运算结果并不依赖变量的当前值,或者能够确保只有单一的线程修改变量的值。
- 变量不需要与其他的状态变量共同参与不变约束。
对于volatile变量的第二个语义是进制指令重排优化。普通变量仅仅会保证执行代码结果的地方都会得到正确的结果,但是不能保证会在执行方法的过程中会按具体操作的执行顺序和你写的代码的顺序一致。在硬件方面上讲,CPU允许多条指令不按程序规定的顺序分发给各个电路单元处理。但是并不是任意的顺序执行,是在保证最后能够正确的处理指令之间的依赖情况,也就是最后结果是正确的前提下。
那volatile是如何禁止指令重排的呢?
被volatile修饰的变量赋值后多执行了lock 地址 操作,这个操作相当于设置了一个内存屏障(内存屏障的作用就是不能把后面的指令重排序到内存屏障之前的位置),当只有一个CPU访问内存的时候,不需要内存屏障,但是如果有两个或更多的CPU访问同一块内存,且其中一个在观测另一个CPU访问,这时候就需要内存屏障保证一致性。它使得CPU中的缓存写入到内存,在这个过程中会让其他CPU或者内核无效化他们自己内存中的被volatile修饰的变量。
对于long和double变量的特殊规则
对于64位的数据类型,在模型中特别定义了 一条相对宽松的规定:允许虚拟机在没有被volatile修饰的long / double类型的变量可以不保证实现原子性(可以划分为对其进行两次32的数据操作),这就是非原子协定。
因为Java内存模型虽然允许虚拟机不把long和double变量的读写操作规定成原子性的,但是虚拟机可以选择把这些操作实现成原子性的操作,虚拟机有这个选择的权利,所以在实际开发中,各个平台的商用虚拟机几乎都选择把64位数据的读写操作作为原子性操作来对待,所以一般不需要把long和double变量专门声明为volatile。
原子性、可见性、有序性
其实Java内存模型具体围绕的就是如何处理这三个特性。
原子性:由Java内存模型保证原子性的操作包括read、 load、 assign、 use、 store、 write。我们大致可以认为这些操作对基本的数据类型都是具备原子性的。如果需要一个大范围的原子操作,Java内存模型提供了lock和unlock操作满足这种需求,还提供了更高层次的直接字节码指令monitorenter和monitorexit来隐式的使用这两个操作,这两个字节码指令反应到Java代码中就是同步块—synchronized关键字。
可见性:可见性是指当一个线程修改了共享变量的值,其他线程能够感知这个修改。JMM是通过在变量修改后将新值同步回主存,在变量读取前从主内存刷新变量值这种依赖主内存作为传递媒介的方式实现可见性的,无论是普通变量还是volatile变量都是如此,但volatile修饰的变量保证了新值能够立即同步到主内存,以及每次使用前从主内存刷新。因此,可以说volatile保证了多线程操作时变量的可见性,而普通变量不能保证这一点。除volatile外还有synchronized和final也可保证内存的可见性。
有序性:Java提供了volatile和synchronized两个关键字来保证线程之间操作的有序性,volatile关键字本身包含了禁止指令重排的语义,而synchronized是:一个变量在同一时刻只允许一条线程对其进行lock操作。这条规则规定了持有同一把锁的两个同步块只能串行执行。天然的有序性在Java中规定的是:如果在本线程观察的话,所有的操作都是有序的,如果在另外的线程观察另一个线程,所有的操作都是无序的。
先行先发生原则(happen-before原则)
先行先发生是指:
Java内存模型中定义的两项操作之间的偏序关系,如果说A先行于B,其实就是说在发生B操作之前,操作A产生的影响能被操作B观察到,至于这个影响可以是修改内存中的共享变量也可以是发送消息、调用某个方法等。
happen-before要求前一个操作的执行结果对后一个操作可见,并且前一个操作按照顺序排在第二个操作之前。
happen-before规则:
- 程序次序规则:在一个线程内,按照程序代码顺序,书写在前面的操作要先行发生于书写在后面的操作。准确的说,应该是控制流顺序而不是程序代码的顺序。
- 管程锁定规则:一个unlock操作要先行发生于lock。这里需要强调的是通一把锁。
- volatile变量规则:对一个volatile变量的写操作线性发生于后面对这个变量的读操作,后面是指时间的先后顺序。
- 线程启动规则:Thread对象的start方法先行发生于此线程的每个动作。
- 线程终止规则:线程中的所有操作都先行发生于对此线程的终止检测,比如线程A中执行ThreadB.join();name线程B中的任意操作先行于A从ThreadB.join()操作成功返回。
- 线程中断规则:对线程Thread.interrupt方法的调用先行发生于被中断线程的代码检测到中断事件的发生。
- 对象终结规则:一个对象的初始化完成要先行于他的finalize()方法的开始。
- 传递性:如果操作A先行发生于操作B,操作B先行发生于操作C,那就可以得出操作A先行发生于C。
时间先后顺序与happen-before原则之间基本没有太大的关系,所以我们衡量并发安全问题的时候不要受到时间顺序的干扰,一切以happen-before原则为准。
参考 :《深入理解Java虚拟机 》周志明
Java内存模型与线程(一)的更多相关文章
- java内存模型与线程(转) good
java内存模型与线程 参考 http://baike.baidu.com/view/8657411.htm http://developer.51cto.com/art/201309/410971_ ...
- Java并发程序设计(三) Java内存模型和线程安全
Java内存模型和线程安全 一 .原子性 原子性是指一个操作是不可中断的.即使是在多个线程一起执行的时候,一个操作一旦开始,就不会被其它线程干扰. 思考:i++是原子操作吗? 二.有序性 Java代 ...
- 深入理解java虚拟机-第12章Java内存模型与线程
第12章 Java内存模型与线程 Java内存模型 主内存与工作内存: java内存模型规定了所有的变量都在主内存中,每条线程还有自己的工作内存. 工作内存中保存了该线程使用的主内存副本拷贝,线程对 ...
- jvm(12)-java内存模型与线程
[0]README 0.1)本文部分文字描述转自“深入理解jvm”,旨在学习“java内存模型与线程” 的基础知识: [1]概述 1)并发处理的广泛应用是使得 Amdahl 定律代替摩尔定律称为计 ...
- (Java多线程系列七)Java内存模型和线程的三大特性
Java内存模型和线程的三大特性 多线程有三大特性:原子性.可见性.有序性 1.Java内存模型 Java内存模型(Java Memory Model ,JMM),决定一个线程对共享变量的写入时,能对 ...
- 深入理解Java虚拟机(第三版)-13.Java内存模型与线程
13.Java内存模型与线程 1.Java内存模型 Java 内存模型的主要目的是定义程序中各种变量的访问规则,即关注在虚拟机中把变量值存储到主内存和从内存中取出变量值的底层细节 该变量指的是 实例字 ...
- 一夜搞懂 | Java 内存模型与线程
前言 本文已经收录到我的 Github 个人博客,欢迎大佬们光临寒舍: 我的 GIthub 博客 学习导图 一.为什么要学习内存模型与线程? 并发处理的广泛应用是 Amdah1 定律代替摩尔定律成为计 ...
- 《深入了解java虚拟机》高效并发读书笔记——Java内存模型,线程,线程安全 与锁优化
<深入了解java虚拟机>高效并发读书笔记--Java内存模型,线程,线程安全 与锁优化 本文主要参考<深入了解java虚拟机>高效并发章节 关于锁升级,偏向锁,轻量级锁参考& ...
- java内存模型和线程
概述 多任务的处理在现在的计算机中可以说是"标配"了,在许多的情况下,让计算机同时做几件事情,不仅是因为计算机的运算能力的强大,还有一个重要的原因是:cpu的运算速度和计算机的存储 ...
随机推荐
- P6007 [USACO20JAN]Springboards G
本题解仅用与作者加深算法理解,也欢迎大家的阅读 做题背景 原本关于二维的点的 \(dp\) 问题一直都没有什么想法,昨天晚上再做一道 \(cdq\) 的题目的时候被同学询问了这道题,发现可以用二维偏序 ...
- C++异常之七 标准库里的异常类
标准库里的异常类 C++标准提供了一组标准异常类,这些类以基类 Exception 开始,标准程序库抛出的所有异常,都派生于该基类,这些类构成如图所示的异常类的派生继承关系,该基类提供一个成员函数 w ...
- Pytest 系列(27)- allure 命令行参数
如果你还想从头学起Pytest,可以看看这个系列的文章哦! https://www.cnblogs.com/poloyy/category/1690628.html 先看看 allure 命令的帮助文 ...
- [水题日常]Luogu1462 通往奥格瑞玛的道路
QwQ马上高二啦不能颓啦-知乎上听说写博客的效果挺好的,来试一下好啦~ 题目链接<< 题目描述 在艾泽拉斯,有n个城市.编号为1,2,3,...,n. 城市之间有m条双向的公路,连接着两个 ...
- PHP可变变量特性
可变变量 有时候使用可变变量名是很方便的.就是说,一个变量的变量名可以动态的设置和使用.一个普通的变量通过声明来设置,例如: <?php$a = 'hello';?> 一个可变变量获取了一 ...
- 给小白整理的一篇Python知识点
1.基本概念 1.1 四种类型 python中数有四种类型:整数.长整数.浮点数和复数. python中数有四种类型:整数.长整数.浮点数和复数. 整数, 如 1 长整数 是比较大的整数 浮点数 如 ...
- 【程序包管理】篇章3:CentOS平台下软件包安装方法总结
1.软件包安装前的学习:程序包管理的基础知识 2.程序包安装的方法介绍: rpm文件的二进制包安装:Linux程序包管理之rpm安装总结 yum安装:Linux程序包管理之yum源安装 链接:本地yu ...
- 高性能MySQL学习总结一
一.MySQL逻辑架构 第一层的服务不是MySQL独有的,大多数是基于网络的客户端/服务端的工具,如连接处理.授权认证.安全等等. 第二层就是MySQL的核心功能,包括查询解析.分析.优化.缓存以及所 ...
- 渗透工具Burp Suite浅析
Burp suite是一款Web安全领域的跨平台工具,基于Java开发.它集成了很多用于发现常见Web漏洞的模块,如Proxy,Spider,Scanner,Intruder,Repeater等.所有 ...
- [leetcode712]202. Happy Number判断快乐数字
题目很简单,就是用哈希表存,判断有没有重复 学到了:java中字符串的比较有两种: 1.==这种是比较引用,只用两个字符串变量指向同一个地址才相等 2..equals()这种是值的比较,只要两个字符串 ...