深入理解JMM及JVM内存模型知识体系机制
并发编程的难题和挑战
在并发编程的技术领域中,对于我们而言的难题主要有两个:多线程之间如何进行通信和线程之间如何同步,通信是指线程之间以何种机制来交换信息。多线程的线程通信机制
在命令式编程中,线程之间的通信机制有两种:共享内存和消息传递。共享内存的方式,多线程之间共享公共的状态(变量),那么线程之间通过写/读内存中的公共状态(变量)来隐式进行通信。在此模式下,同步实现是隐式进行的,由于消息的发送必须在消息的接收之前。消息传递的方式,多线程之间没有公共的状态(变量),那么线程之间必须通过明确的传递状态(变量)来显式进行通信。在此模式下,同步实现是显式进行的,必须显式指定某个方法或某段代码需要在线程之间互斥执行。Java中的同步模式是什么?
同步机制是指程序用于控制不同线程之间操作发生相对顺序的机制。
Java生态中的并发编程模型采用的是共享内存模型,因此在Java线程之间的通信总是隐式进行, 整个通信过程对开发者是黑盒的,如果编写多线程程序的开发者不深入理解这种隐式模式下的线程之间通信机制,就会会出现内存可见性和一致性的问题,我们统称为线程不安全问题。存在内存可见问题
Java应用程序中, 所有实例域、静态域和数组元素存储在堆内存中, 堆内存在线程之间共享。会存在这内存可见性问题。不存在内存可见问题
局部变量(Local variables) , 方法定义参数(java语言规范称之为formal method parameters) 和异常处理器参数(exception handler parameters) 不会在线程之间共享,它们不会有内存可见性问题,也不受内存模型的影响。
所以,我们在开发多线程场景下的程序的时候主要需要关注的就是内存可见问题变量,包含:实例域、静态域和数组元素。
而为了降低并发编程的难度和门槛,这些线程之间的数据同步和通信控制就交由一个特定的数据模型进行控制和管理,我们称之为Java内存模型(JMM)。Java内存模型(JMM)
JMM决定在程序运行中,一个线程对共享变量的写入何时对另一个线程可见。JMM定义了线程和主内存之间的抽象关系
线程之间的共享变量存储在主内存中,每个线程都有一个私有的本地内存 , 本地内存中存储了该线程以读/写共享变量的副本。
本地内存是JMM的一个抽象概念, 并不真实存在。它涵盖了缓存, 写缓冲区, 寄存器以及其他的硬件和编译器优化。
Java 内存模型的抽象示意图如下:
由上图可见,线程A与线程B之间如要数据通信,需要有以下两个步骤:线程A把本地内存A中更新过的共享变量刷新到主内存中去。线程B到主内存中去读取线程A之前已更新过的共享变量。
下面通过示意图来说明这两个步骤:
如上图所示,本地内存A和B有主内存中共享变量x的副本。假设初始时,这三个内存中的x值都为0。线程A在执行时,把更新后的x值,临时存放在自己的本地内存A中。线程A和线程B需要通信时,线程A首先会把自己本地内存中修改后的x值刷新到主内存中,此时主内存中的x值变了。线程B到主内存中去读取线程A更新后的x值,此时线程B的本地内存的x值也变了。
总结一下就是,这两个步骤数据角度而言是线程A在向线程B发送消息,而且这个通信过程必须要经过主内存。JMM通过控制主内存与每个线程的本地内存之间的交互, 来为程序提供内存可见性保证。线程不安全因素之一(指令重排序问题)
基于上述所说的场景之下,JVM为了在执行程序时为了提高性能,编译器和处理器常常会对指令做重排序。在此我们将按照重排序的执行时间前后分为重排序分三种类型,如下图所示。
第一步属于编译器重排序:编译器优化的重排序,编译器在不改变单线程程序语义的前提下,可以重新安排语句的执行顺序。第二步属于处理器重排序:指令级并行的重排序,现代处理器采用了指令级并行技术(Instruction-Level Parallelism, ILP) 来将多条指令重叠执行。如果不存在数据依赖性, 处理器可以改变语句对应机器指令的执行顺序。第三步属于处理器重排序:内存系统的重排序。由于处理器使用缓存和读/写缓冲区,这使得加载和存储操作看上去可能是在乱序执行,此处特别是针对与本地内存和共享主存之间的更新操作的一致性和可见性
这些重排序都可能会导致多线程程序出现内存可见性问题。JMM解决重排序的线程不安全问题解决编译器级别重排序JMM的编译器重排序规则会禁止特定类型的编译器重排序,此处注意:不是所有的编译器重排序都要禁止。解决处理器级别重排序JMM的处理器重排序规则会要求java编译器在生成指令序列时, 插入特定类型的内存屏障(memory barriers, 也可以称之为memory fence)指令, 通过 内存屏障 指令来禁止特定类型的处理器重排序,此处注意:不是所有的处理器重排序都要禁止)。
总结一下,针对于JMM属于语言级的内存模型, 它确保在不同的编译器和不同的处理器平台之上,通过禁止特定类型的编译器重排序和处理器重排序,从而实现了内存的可见性以及一致性。处理器重排序与内存屏障指令
上面说了其实是通过插入了内存屏障指令,从而控制住了对应的处理器级别的指令重排。线程不安全因素之一(写缓存处理模式)现代的处理器使用写缓冲区来临时保存向内存写入的数据,写缓冲区可以保证指令流水线持续运行,它可以避免由于处理器停顿下来等待向内存写入数据而产生的延迟。通过以批处理的方式刷新写缓冲区,以及合并写缓冲区中对同一内存地址的多次写,可以减少对内存总线的占用。虽然写缓冲区有这么多好处,但每个处理器上的写缓冲区,仅仅对它所在的处理器可见。
这个特性会对内存操作的执行顺序产生重要的影响,处理器对内存的读/写操作的执行顺序,不一定与内存实际发生的读/写操作顺序一致。
处理器A和处理器B可以同时把共享变量写入自己的写缓冲区(A1,B1)从内存中读取另一个共享变量(A2,B2)最后才把自己写缓存区中保存的脏数据刷新到内存中(A3,B3)。
从内存操作实际发生的顺序来看,直到处理器A执行A3来刷新自己的写缓存区,写操作A1才算真正执行了。虽然处理器A执行内存操作的顺序为:A1->A2,但内存操作实际发生的顺序却是:A2->A1。此时,处理器A的内存操作顺序被重排序了(处理器B的情况和处理器A一样)。
由于现代的处理器都会使用写缓冲区,因此现代的处理器都会允许对写-读操作重排序。常见的处理器都允许Store-Load重排序,常见的处理器都不允许对存在数据依赖的操作做重排序。内存屏障指令
为了保证内存可见性, java编译器在生成指令序列的适当位置会插入内存屏障指令来禁止特定类型的处理器重排序。JMM把内存屏障指令分为下列四类:
内存屏障类型
指令示例
备注
LoadLoad Barries
Load1LoadLoadLoad2
确保Load1数据的装载,之前于Load2及所有后续装载指令的装载
StoreStore Barries
Store1StoreStoreStore2
确保Store1数据对其他处理器可见(刷新到内存),之前于Store2及所有后续存储指令的存储。
LoadStore Barriers
Load1 LoadStoreStore2
确保Load1数据装载, 之前于Store2及所有后续的存储指令刷新到内存
StoreLoad Barriers
Store1StoreLoadLoad2
确保Storel数据对其他处理器变得可见(指刷新到内存),之前于Load2及所有后续装载指令的装载。StoreLoad Barriers会使该屏障之前的所有内存访问指令(存储和装载指令)完成之后,才执行该屏障之后的内存访问指令。
**StoreLoad Barriers是一个"全能型"的屏障, 它同时具有其他三个屏障的效果。现代的多处理器大都支持该屏障(其他类型的屏障不一定被所有处理器支持)。执行该屏障开销会很昂贵,因为当前处理器通常要把写缓冲区中的数据全部刷新到内存中(buffer fully flush) **。
女人花如果说青春象一首浪漫的诗歌,节奏明快,旋律生辉,恰似春光明媚那么女人则应该是一篇抒情散文,情愫幽幽,蕴涵深邃,令人会心耐读。宁可抱香枝上老,不随黄叶舞秋风,珍惜这份上苍赐予的华美礼
千与千寻你看懂了吗最近,我又重温了一遍宫崎骏的千与千寻,发现不管过去多久,宫崎骏笔下的世界都蕴含着一股温暖人心的力量。小时候看不懂千与千寻无脸男小时候希望长大,长大后却想回到小时候,那时候穷,却笑得
帮人之道别人不求,自己不帮多少仁善之人,因为主动帮人,尽心帮人,总为别人着想,而遭人嫌弃,被人轻视,落得心冷情凄!帮人于危难之时,人会感激你帮人于平顺之时,人会轻视你,古今如此。智慧的人,在别人危难之时帮他
松树二点零冬日生活打卡季现如今不到一个月,今天是活动一段落结束的日子。如我所想,我参加活动并不真以为能挣到钱,事实也是如我所想,但心中还是非我所愿。但我以为,倒是有一个最大的收获,那就是,我
成绩属于过去,未来还在远方,拼搏当下最为重要鸡蛋从外面分开是食物,从里面分开是生命。人生为什么不是这样呢?你是软弱的人还是坚强的人,自己来改变。没有人能代替你成长。没有人能帮助坚强的人。没有人的人生一帆风顺,谁都有着别人看不
五味杂陈诗写运城美食(第五季)作者冯建国题记中国素以美食大国享誉世界,各种美味佳肴遍布全国各地,成为一道道亮丽的文化风景,每一个中国人舌尖的故乡构成了整个中国。山西运城作为华夏文明最古老的发祥地之一,也有着深厚
这么快就倒闭了,到年底,不知道还会倒闭多少家店头条创作挑战赛今天上班的路上,看到楼下一家刚新开不久的汤粉店贴出来了转让的信息,记得这个是前两个月刚开的,那个时候生意还是比较旺的,没想到这么快就倒闭了,难道年底的餐饮倒闭潮已经提
花生米不要油炸了,试试这个新鲜做法,吃光一盘都不过瘾,真解馋花生米不要油炸了,试试这个新鲜做法,吃光一盘都不过瘾,真解馋。花生米本身价格不贵,对身体好处多多,所以不管是主食凉菜零食,都能见到花生米。油炸花生米是最常见的吃法,又香又脆,大人孩
这几道点心,叫做惊鸿一瞥上周去了醉西湖,吃了一桌子地道的杭州本帮菜。菜很好吃,其中更是有两道点心戳中了我。一道叫做松鼠之恋,全都是可可爱爱的小松鼠们。另外一道是小刺猬(我把这道点心的名字给忘了,就暂且叫做
多伦多印象到达多伦多市是当地时间晚上6点多一点。天早已经黑了(多伦多这个季节四点多就天黑了),看不清具体的景色,只有夜色下一片灯火通明。加拿大的海关官员问了几个简单的问题就放行了,省去很多麻
自动驾驶的未来究竟属于单车智能还是车路协同?毫末智行给出答案临近年底,自动驾驶行业并不太平,各类撤资裁员的消息如雪花般漫天飞舞,给人一种凛冬已至的感觉。这不仅拖延了自动驾驶产业的发展进度,也让原本就围绕于自动驾驶产业的质疑声变得越发明亮在此