并发编程之Java内存模型

@TOC

5.1 Java内存模型

JMM即Java Memory Model,它定义了主存、工作内存抽象概念,底层对应着CPU寄存器、缓存、硬件内存、CPU指令优化等。
JMM体现在以下几个方面

  • 原子性 - 保证指令不会受到线程上下文切换的影响
  • 可见性 - 保证指令不会受cput缓存的影响
  • 有序性 - 保证指令不会受cpu指令并行优化的影响

5.2 可见性

退不出的循环
先来看一个现象,main线程对run变量的修改对于t线程不可见,导致了t线程无法停止 :


在这里插入图片描述

为什么呢?分析一下 :
1.初始状态,t线程刚开始从主内存读取了run的值到工作内存。


在这里插入图片描述
  1. 因为t线程要频繁从主内存中读取run的值,JIT编译器会将run的值缓存至自己工作内存中的高速缓存中,减少对主存中run的访问,提高效率


    在这里插入图片描述
  2. 1秒之后,main线程修改了run的值,并同步至主存,而t是从自己工作内存中的高速缓存中读取这个变量的值,结果永远是旧值
    在这里插入图片描述

    解决方法
    volatile(易变关键字)
    它可以用来修饰成员变量和静态成员变量,它可以避免线程从自己的工作缓存中查找变量的值,必须到主存中获取它的值,线程操作volatile变量都是直接操作主存。
    可见性 VS 原子性
    前面例子体现的实际就是可见性,它保证的是在多个线程之间,一个线程对volatile变量的修改对另一个线程可见,不能保证原子性,仅用在一个写线程,多个读线程的情况 :
    上例从字节码理解是这样的 :
    在这里插入图片描述

    比较一下之前我们将线程安全时举的例子 :两个线程一个i++ 一个i--,只能保证看到最新值,不能解决指令交错
    在这里插入图片描述

    注意
    synchronized语句块既可以保证代码块的原子性,也同时保证代码块内变量的可见性。但缺点是synchronized是属于重量级操作,性能相对更低。
    如果在前面示例中的死循环中加入System.out.println()会发现即使不加volatile修饰符,线程t也能正确看到对run变量的修改了,想一想为什么?

5.3 有序性

JVM会在不影响正确性的前提下,可以调整语句的执行顺序 :


在这里插入图片描述

可以看到,至于是先执行i还是先执行j,对最终的结果不会产生影响。所以,上面代码真正执行时,既可以是


在这里插入图片描述

也可以是
在这里插入图片描述

这种特性称之为指令重排,多线程下指令重排会影响正确性。

volatile原理

volatile的底层实现原理是内存屏障,Memory Barrier(Memory Fence)

  • 对volatile变量的写指令后后加入写屏障
  • 对volatile变量的读指令前会加入读屏障
  1. 如何保证可见性
  • 写屏障(sfence)保证在该屏障之前的,对共享变量的改动,都同步到主存当中


    在这里插入图片描述
  • 而读屏障(lfence)保证在该屏障之后,对共享变量的读取,加载的是主存中最新的数据


    在这里插入图片描述

    在这里插入图片描述

    2.如何保证有序性

  • 写屏障会确保指令重排序时,不会将写屏障之前的代码排在写屏障之后


    在这里插入图片描述
  • 读屏障会确保指令重排序时,不会将读屏障之后的代码排在读屏障之前


    在这里插入图片描述

    在这里插入图片描述

    不能解决指令交错 :

  • 写屏障仅仅是保证之后的读能够读到最新的结果,但不能保证读跑到它前面去
  • 而有序性的保证也只是保证了本线程内相关代码不被重排序


    在这里插入图片描述

double-checked locking 单例模式为例

在这里插入图片描述

以上的实现特点是 :

  • 懒惰实例化
  • 首次使用getInstance()才使用synchronized加锁,后续使用时无需加锁
  • 有隐含的,但很关键的一点 : 第一个if使用了INSTANCE变量,是在同步块之外
    但在多线程环境下,上面的代码是有问题的,getInstance方法对应的字节码为 :


    在这里插入图片描述

    其中

  • 17表示创建对象,将对象引用入栈 // new Singleton
  • 20表示复制一份对象引用 // 引用地址
  • 21表示利用一个对象引用,调用构造方法 // 根据引用地址调用
  • 24表示利用一个对象引用,赋值给 static INSTANCE
    也许jvm会优化为 : 先执行24,再执行21.如果两个线程t1,t2按如下时间序列执行 :


    在这里插入图片描述

    关键在于 0 :getstatic这行代码在monitor控制之外,它就像之前举例中不守规则的人,可以越过monitor读取INSTANCE变量的值
    这时t1还未完成将构造方法执行完毕,如果在构造方法中要执行很多初始化操作,那么t2拿到的是将是一个未初始化完毕的单例
    对INSTANCE使用volatile修饰即可,可以禁用指令重排,但要注意在JDK5以上版本的volatile才会真正有效

4.double-checked locking 解决

在这里插入图片描述

字节码上看不出来volatile指令的效果
在这里插入图片描述

在这里插入图片描述

happens-before
happens-before规定了对共享变量的写操作对其它线程的读操作可见,它是可见性与有序性的一套规则总结,抛开以下happens-before规则,JMM并不能保证一个线程对共享变量的写,对于其它线程对该共享变量的读可见

  • 线程解锁m之前对变量的写,对于接下来对m加锁的其它线程对该变量的读可见


    在这里插入图片描述
  • 线程对volatile变量的写,对接下来其它线程对该变量的读可见


    在这里插入图片描述
  • 线程start前对变量的写,对该线程开始后对该变量的读可见


    在这里插入图片描述
  • 线程结束前对变量的写,对其它线程得知它结束后的读可见(比如其它线程调用t1.isAlive()或t1.join()等待它结束)


    在这里插入图片描述
  • 线程t1打断t2(interrupt)前对变量的写,对于其他线程得知t2被打断后对变量的读可见(通过t2.interrupted或t2.interrupted)


    在这里插入图片描述
  • 对变量默认值(0,false,null)的写,对其它线程对该变量的读可见
  • 具有传递性,如果x hb -> z 那么有x hb -> z,配合volatile的防指令重排,有下面的例子


    在这里插入图片描述

    变量都是指成员变量或静态成员变量

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

推荐阅读更多精彩内容