第三章 Java内存模型之基础③

平时我们很少会注意Java内存模型,对于一些概念很多都是背诵,不是甚解,纳闷这一章,将把这个透明层给扯开,让他再也遮不住我们眼睛。

首先两个关键问题

1)线程之间如何通信
2)线程之间是如何同步

线程之间的通信机制有两种:共享内存和消息传递。

在共享内存的并发模型中,线程之间共享程序的公共状态,通过读-写内存中的公共状态进行隐式通信。
在消息传递的并发模型中,线程之间没有公共状态,线程之间必须通过发送消息来进行显示通信。

同步是指程序中用于控制不同线程间发生相对顺序的机制,在共享内存并发模型里,同步是显示进行的。程序员必须显示指定某个方法或某段代码需要在线程之间互相执行。在消息传递的并发模型里,由于消息的发送必须在消息的接受之前,因此同步是隐式进行的。

那么Java的并发采用的共享内存模型,Java线程之间的通信是隐式进行的。整个通信的过程对程序员是完全透明的。如果我们不理解这个隐式进行的线程之间的这种通信,那么遇到各种奇怪的内存问题也就不奇怪了。

Java内存模型的抽象结构

首先,我们要了解一下内存可见性问题发生的位置。在Java中,所有实例域和数组元素都存储在堆内存中(重点),堆内存在线程之间共享。而局部变量,方法定义参数和异常处理的参数不会在线程之间共享的,那是线程私有的,所以他们不会有内存可见性问题,也不受内存模型的影响。

Java线程之间的通信由Java内存模型(JMM)控制。JMM决定一个线程对共享变量的写入何时对另一个线程可见。从抽象的角度来说,JMM定义了线程和主内存之间的抽象关系。线程之间的共享变量存储在主内存中(Main Memory)每个线程都有一个私有的本地存储(Local Memory),本地内存中存储了该线程以读/写共享变量的副本。本地内存是JMM的一个抽象概念,并不真实存在。它涵盖了缓存、写缓冲区、寄存器以及其他的硬件和编译器优化。就是我们在理解中可以想象有一个本地存储这个东西,方便我们去理解和想象,但是实际是缓存、写缓冲区、寄存器等形式。

如下图一般,我们就能很好地理解这个概念了。


从图中我们可以发现:如果线程A要与线程B通信的话,需要下面两个过程:
1)线程A把本地内存中A中更新的共享变量刷新到主内存中
2)线程B到主内存中去读取线程A已经更新过的共享变量

我们会发现,共享内存之间的通信确实是隐性的。

当然我们在举个小栗子,更直观的去理解。


相信大家都看懂了吧。线程A向线程B发送消息,必须通过主内存,JMM通过控制主内存和每个线程的本地内存之间的交互,来为Java程序员提供内存可见性保证。

从源代码到执行序列的重排序

相信大家都听过重排序这个名词,就是在执行程序的时候,为了提高性能,编译器和处理器常常会对指令做重排序。

重排序分为三种:
1)编译器优化的重排序。编译器在不改变单线程程序语句的前提下,可以重新安排语句的执行顺序。
2)指令级并行的重排序。现代处理器采用了指令级并行技术来将多条指令重叠执行。如果不存在数据的依赖性,处理器可能改变语句对应的机器指令的执行顺序。
3)内存系统的重排序。由于处理器使用了缓存和读/写缓冲区,这使得加载和存储操作看上去是乱写执行。

从Java源代码到最终执行的指令序列,会分别经历下面3种重排序。



其中1属于编译器重排序,2和3属于处理器重排序。这些排序都有可能造成多线程程序出现内存可见性问题。
对于编译器JMM的编译器重排序规则可以禁止特定类型的编译器重排序。
对于处理器则需要通过内存屏障指令来禁止特定类型的处理器重排序。
JMM属于语言级的内存模型,他确保在不同的编译器和不同的处理器上,通过禁止特定类型的编译器重排序和处理器重排序,为我们提供一致的内存可见性保证。

接下来讲点有趣的而且很有用的东西。比上面的好玩很多。

并发编程模型的分类

背景:
现代的处理器使用写缓冲区临时保存向内存写入的数据。写缓冲区可以保证指令流水线持续运行,避免由于处理器停顿下来等待内存写入数据而产生的延迟(cpu比内存块太多了,所以CPU会经常等待内存写入数据)。同时,通过以批处理的方式刷新写缓冲区,以及合并写缓冲区对同一内存地址的多次写,减少对内存总线的占用。我们发现写缓冲区的优点很给力啊,但是···它有一个很严重的问题,因为每个处理器都有自己的写缓冲区,而且只对它所在的处理器可见。这一特性会对内存操作的执行顺序带来重要的影响。处理器对内存的读/写操作的执行顺序,不一定与内存实际发生的读/写操作顺序一致。

不太懂不要紧,来一口小栗子马上懂

线程A、B分别进行A1、A2和B1、B2的操作,如果是一个线程执行这几个操作,是不会有问题的,但是两个线程,分别进行操作,因为重排序造成执行顺序的不确定性,我们很有可能得到x=y=0 的结果。

其中步骤如下图,首先同时A1、B1 写缓冲区操作之后,直接读取共享变量(A2、B2)。最后才会把脏数据刷新。这种顺序就会得到x=y=0 的结果。


这种顺序发生的关键在于写缓冲区仅对自己的处理器可见,他会导致处理器执行内存操作的顺序可能会与内存实际的操作顺序不一样。由于现代的处理器都会使用写缓冲区,因此现代的处理器都会允许对写-读操作进行重排序。

重点就是现代处理器允许读-写操作进行重排序。


image.png

总结上图的结论 :常见的处理下允许Store-Load重排序,这个不影响结果。但是不允许数据依赖的操作进行重排序。

为了保证内存的可见性,Java编译器在生成指令序列的适当位置会插入内存屏障指令来禁止特定类型的处理器重排序。大家了解一下就好了。不必太多纠结。

image.png

最后,介绍一个和我们息息相关的透明知识点。

happens-before

从JDK5开始,Java采用JSR-133内存模型,JSR-133使用happens-before的概念来阐述操作之间的内存可见性。在JMM中,如果一个操作执行的结果需要对另一个操作可见,那么这两个操作之间必须存在happens-before关系。这里提到的两个操作既可以在一个线程里也可以是在不同的线程之间。

happens-before规则:

1)程序顺序规则:一个线程中的每个操作,happens-before于该线程中的任意后续操作。
意思就是当在单线程中,程序中的逻辑上前一个操作必须happens-before后一个操作。
2)监视器锁规则:对一个锁的解锁,happens-before于随后对这个锁的加锁
3)volatile变量规则:对一个volatile域的写,happens-before于任意后续对这个volatile域的读
4)传递性:如果A happens-before B,且B happens-before C,那么A happens-before C。

需要注意的是:两个操作之间具有happens-before关系,并不意味前一个操作必须要在后一个操作之前执行!!! happens-before只是要求前一个操作(执行的结果)对后一个操作可见,且前一个操作按顺序排在第二个操作之前(the first is visible to and ordered before the second)

还是很绕对吧,就是这样的如果

   public void add(){
    int a = 1;//A
    int b= 2;//B
    int c = a+b;//C
    }

A happens-before B ,A happens-before C,B happens-before C。
因为A happens-before B,所以A操作产生的结果一定要对B操作可见,但是B操作和A没关系,所以可以重排序。同样A happens-before C,所以A操作产生的结果一定要对C操作可见,如果重排序了结果会错误,所以不能重排序。

再来体会一遍:如果一个操作执行的结果需要对另一个操作可见,那么这两个操作之间必须存在happens-before关系。两个操作之间具有happens-before关系,并不意味前一个操作必须要在后一个操作之前执行!happens-before只是要求前一个操作(执行的结果)对后一个操作可见,且前一个操作按顺序排在第二个操作之前。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,098评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,213评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,960评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,519评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,512评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,533评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,914评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,574评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,804评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,563评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,644评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,350评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,933评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,908评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,146评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,847评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,361评论 2 342

推荐阅读更多精彩内容