资讯专栏INFORMATION COLUMN

JMM——Java内存模型

keithyau / 1076人阅读

摘要:讲什么内存模型描述了多个线程之间通过内存交互的规范,屏蔽了各种硬件和操作系统的访问差异的,保证了程序在各种平台下对内存的访问都能保证效果一致。这个版本的内存模型在中仍然在使用。

JMM讲什么

内存模型(Memory Model)描述了多个线程之间通过内存交互的规范,屏蔽了各种硬件和操作系统的访问差异的,保证了Java程序在各种平台下对内存的访问都能保证效果一致。在现代的多处理器(多核处理器)系统中,处理器拥有多级缓存以提升内存访问速度同时减少了内存总线的访问量。变量最终会保存在内存中,但是编译器、运行时、处理器可以对指令优化和重新排序,缓存、寄存器也对内存进行了读写优化,只要保证在单个线程内行为与代码顺序串行语义相同即可。内存模型定义了充分且必要的条款,描述了程序中变量之间的关系,以及变量的读取、写入的底层细节,实现了并发过程中的原子性、可见性、有序性。

老版本JMM中的问题

原始的Java内存模型存在一些不足,因此Java内存模型在Java 1.5时被重新修订(JSR133)。这个版本的Java内存模型在Java 8中仍然在使用。老版本中的问题有:

final字段的值并不是完全不变的。构造器中对final字段值的写入可以重排序至构造函数返回并将对象引用赋值给变量之后,导致其它线程看到还未完成初始化的final字段。这个问题的经典案例是String的早期实现中,有多个final字段,但是其它线程可以看到字符串长度为0,而实际上字符串长度并不为0。

volatile字段的写操作与非volatile字段的读写操作重排序。由于重排序的缘故,volatile字段的写操作之前的操作被重新排序至之后进行,导致其它线程看到的结果与程序代码不一致。这个问题的经典案例是Double-Checked Locking(也称multi-threaded singleton pattern)问题,即通过一个volatile字段来判断是否需要进行初始化,从而实现延迟初始化并减少锁操作的性能损耗。由于重排序的问题,导致部分初始化操作或构造操作被排序至volatile字段写操作之后,导致其它线程看到部分初始化的数据,破坏了数据的一致性。

JSR133解决的问题

volatile语义增强:volatile字段的读、写操作与其它任务内存操作操作重排序,volatile的读操作与监视器锁的获取具有相同的内存语义(缓存失效并从主存重新读取),volatile的定操作与监视器锁的释放具有相同的内存语义(缓存刷入主存)。在这个约定下,线程A写入volatile字段V后,线程B可以读出V的值,同时线程A在写入V时能够看到的变量值对线程B也可见。

是否可以重排序 第二个操作 第二个操作 第二个操作
第一个操作 普通读/普通写 volatile读/monitor enter volatile写/monitor exit
普通读/普通写 No
voaltile读/monitor enter No No No
volatile写/monitor exit No No

其中普通读指getfield, getstatic, 非volatile数组的arrayload, 普通写指putfield, putstatic, 非volatile数组的arraystore。volatile读写分别是volatile字段的getfield, getstatic和putfield, putstatic。monitorenter是进入同步块或同步方法,monitorexist指退出同步块或同步方法。

final字段增强:只要对象正确构造,那么不需要使用同步就可以保证任意线程都能看到final字段在构造器中被初始化之后的值。编译器会在final域的写之后,构造器函数return之前,插入StoreStore屏障。这个屏障禁止处理器把final域的写重排序到构造函数之外。写final域的重排序规则可以保证:在对象引用为任意线程可见之前,对象的final域已经被正确初始化过了,而普通域不具有这个保障。读final域的重排序规则是,禁止处理器重排序初次读取对象引用与初次读取该对象包含的final域这两个操作。编译器会在读final域操作的前面插入一个LoadLoad屏障。读final域的重排序规则可以确保:在读一个对象的final域之前,一定会先读包含这个final域的对象的引用。

初次读对象引用与初次读该对象包含的final域,这两个操作之间存在间接依赖关系。由于编译器遵守间接依赖关系,因此不会重排序这两个操作;大多数处理器也会遵守间接依赖,也不会重排序这两个操作。但有少数处理器允许对存在间接依赖关系的操作做重排序(比如alpha处理器),这个规则就是专门用来针对这种处理器的。

对于引用类型,写final域的重排序规则对编译器和处理器增加了如下约束:

在构造函数内对一个final引用的对象的成员的写入,与随后在构造函数外把这个被构造对象的引用赋值给一个引用变量,这两个操作之间不能重排序

确保在final引用的初始化在构造函数内完成,因此一旦其他线程拿到了一个非null的final引用,那么这个引用一定是在构造函数内被正确赋值的(至于是否正确初始化,则不一定,这取决于final引用的赋值语句)

Happens-Before规则

HappendBefore规则包括:

程序顺序规则: 如果程序中操作A在操作B之前,那么同一个线程中操作A将在操作B之前进行

监视器锁规则: 在监视器锁上的锁操作必须在同一个监视器锁上的加锁操作之前执行

volatile变量规则: volatile变量的写入操作必须在该变量的读操作之前执行

线程启动规则: 在线程上对Thread.start的调用必须在该线程中执行任何操作之前执行

线程结束规则: 线程中的任何操作都必须在其他线程检测到该线程已经结束之前执行

中断规则: 当一个线程在另一个线程上调用interrupt时,必须在被中断线程检测到interrupt之前执行

传递性: 如果操作A在操作B之前执行,并且操作B在操作C之前执行,那么操作A在操作C之前执行

相关链接

https://zhuanlan.zhihu.com/p/29881777

http://www.cs.umd.edu/~pugh/java/memoryModel/

https://www.cs.umd.edu/~pugh/java/memoryModel/jsr-133-faq.html

http://www.cs.umd.edu/~pugh/java/memoryModel/jsr133.pdf

http://gee.cs.oswego.edu/dl/jmm/cookbook.html

https://liuzhengyang.github.io/2017/05/12/javamemorymodel/

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/73961.html

相关文章

  • 深入理解Java内存模型(七)——总结

    摘要:编译器,和处理器会共同确保单线程程序的执行结果与该程序在顺序一致性模型中的执行结果相同。正确同步的多线程程序的执行将具有顺序一致性程序的执行结果与该程序在顺序一致性内存模型中的执行结果相同。 前情提要 深入理解Java内存模型(六)——final 处理器内存模型 顺序一致性内存模型是一个理论参考模型,JMM和处理器内存模型在设计时通常会把顺序一致性内存模型作为参照。JMM和处理器内...

    paney129 评论0 收藏0
  • 《深入理解 Java 内存模型》读书笔记

    摘要:前提深入理解内存模型程晓明著,该书在以前看过一遍,现在学的东西越多,感觉那块越重要,于是又再细看一遍,于是便有了下面的读书笔记总结。同步同步是指程序用于控制不同线程之间操作发生相对顺序的机制。线程之间的通信由内存模型控制。 showImg(https://segmentfault.com/img/remote/1460000013474312?w=1920&h=1271); 前提 《深...

    xuexiangjys 评论0 收藏0
  • 《深入理解 Java 内存模型》读书笔记

    摘要:前提深入理解内存模型程晓明著,该书在以前看过一遍,现在学的东西越多,感觉那块越重要,于是又再细看一遍,于是便有了下面的读书笔记总结。同步同步是指程序用于控制不同线程之间操作发生相对顺序的机制。线程之间的通信由内存模型控制。 showImg(https://mmbiz.qpic.cn/mmbiz_jpg/1flHOHZw6RtPu3BNx3zps1JhSmPICRw7QgeOmxOfTb...

    姘存按 评论0 收藏0
  • Java内存模型

    摘要:内存模型对内存模型的介绍对内存模型的结构图的线程之间的通信是通过共享内存的方式进行隐式通信,即线程把某状态写入主内存中的共享变量,线程读取的值,这样就完成了通信。 Java内存模型(JMM) 1.对内存模型的介绍 ①对Java内存模型的结构图 java的线程之间的通信是通过共享内存的方式进行隐式通信,即线程A把某状态写入主内存中的共享变量X,线程B读取X的值,这样就完成了通信。是一种...

    sherlock221 评论0 收藏0
  • java内存模型

    摘要:顺序一致性内存模型有两大特性一个线程中所有操作必须按照程序的顺序执行。这里的同步包括对常用同步原语的正确使用通过以下程序说明与顺序一致性两种内存模型的对比顺序一致性模型中所有操作完全按程序的顺序串行执行。 java内存模型 java内存模型基础 happen-before模型 JSR-133使用happen-before的概念来阐述操作之间的内存可见性。在JMM中,如果一个操作执行的结...

    2i18ns 评论0 收藏0
  • 深入理解Java内存模型(三)——顺序一致性

    摘要:下面是该程序在两个内存模型中的执行时序对比图在顺序一致性模型中,所有操作完全按程序的顺序串行执行。不保证未同步程序的执行结果与该程序在顺序一致性模型中的执行结果一致。 前情提要 深入理解Java内存模型(二)——重排序 数据竞争与顺序一致性保证 当程序未正确同步时,就会存在数据竞争。java内存模型规范对数据竞争的定义如下: 在一个线程中写一个变量, 在另一个线程读同一个变量,...

    aristark 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<