JMM——Java内存模型

发布时间:2019-11-19 发布网站:脚本宝典
脚本宝典收集整理的这篇文章主要介绍了JMM——Java内存模型脚本宝典觉得挺不错的,现在分享给大家,也给大家做个参考。
@H_512_0@

JMM讲什么

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

老版本JMM中的问题

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

  1. final字段的值并不是完全不变的。构造器中对final字段值的写入可以重排序至构造函数返回并将对象引用赋值给变量之后,导致其它线程看到还未完成初始化的final字段。这个问题的经典案例是String早期实现中,有多个final字段,但是其它线程可以看到字符串长度为0,而实际上字符串长度并不为0。
  2. volatile字段的写操作与非volatile字段的读写操作重排序。由于重排序的缘故,volatile字段的写操作之前的操作被重新排序至之后进行,导致其它线程看到的结果与程序代码不一致。这个问题的经典案例是Double-Checked Locking(也称multi-threaded singleton pattern)问题,即通过一个volatile字段来判断是否需要进行初始化,从而实现延迟初始化并减少锁操作的性能损耗。由于重排序的问题,导致部分初始化操作或构造操作被排序至volatile字段写操作之后,导致其它线程看到部分初始化的数据,破坏了数据的一致性。

JSR133解决的问题

  1. 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指退出同步块或同步方法。

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

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

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

    1. 在构造函数内对一个final引用的对象的成员的写入,与随后在构造函数外把这个被构造对象的引用赋值给一个引用变量,这两个操作之间不能重排序
    2. 确保在final引用的初始化在构造函数内完成,因此一旦其他线程拿到了一个非null的final引用,那么这个引用一定是在构造函数内被正确赋值的(至于是否正确初始化,则不一定,这取决于final引用的赋值语句)

HapPEns-Before规则

HappendBefore规则包括:

  1. 程序顺序规则: 如果程序中操作A在操作B之前,那么同一个线程中操作A将在操作B之前进行
  2. 监视器锁规则: 在监视器锁上的锁操作必须在同一个监视器锁上的加锁操作之前执行
  3. volatile变量规则: volatile变量的写入操作必须在该变量的读操作之前执行
  4. 线程启动规则: 在线程上对Thread.start的调用必须在该线程中执行任何操作之前执行
  5. 线程结束规则: 线程中的任何操作都必须在其他线程检测到该线程已经结束之前执行
  6. 中断规则: 当一个线程在另一个线程上调用interrupt时,必须在被中断线程检测到interrupt之前执行
  7. 传递性: 如果操作A在操作B之前执行,并且操作B在操作C之前执行,那么操作A在操作C之前执行

相关链接

  1. https://zhuanlan.zhihu.com/p/29881777
  2. http://www.cs.umd.edu/~pugh/java/memoryModel/
  3. https://www.cs.umd.edu/~pugh/java/memoryModel/jsr-133-faq.html
  4. http://www.cs.umd.edu/~pugh/java/memoryModel/jsr133.pdf
  5. http://gee.cs.oswego.edu/dl/jmm/cookbook.html
  6. https://liuzhengyang.github.io/2017/05/12/javamemorymodel/

脚本宝典总结

以上是脚本宝典为你收集整理的JMM——Java内存模型全部内容,希望文章能够帮你解决JMM——Java内存模型所遇到的问题。

如果觉得脚本宝典网站内容还不错,欢迎将脚本宝典推荐好友。

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
如您有任何意见或建议可联系处理。小编QQ:384754419,请注明来意。