JDK5引入了JMM新规范:JSR-133,引入了happens-before/可见性等概念,对synchronized/volatile/final等关键词进行了语义定义。解决了:final变量在构造器中初始化的线程安全问题以及volatile变量与no-volatile变量之间的重排序问题。

为什么需要Memory Model

在多线程的场景下,thread1修改了一个变量后,thread2要读取这个变量,其间可能会发生指令执行顺序的问题(因为编译器优化指令、处理器重排指令、写数据缓存未及时更新到主内存)。如何保证thread2要读的变量是想要的thread1修改后的变量呢?
Memory Model 描述了程序中变量以及它们在寄存器、缓存、内存中的关系的问题。即,在没有Momory Model 的情况下,无法保证多线程环境下变量调用的次序问题,有了Memory Model的具体关键词对应的语义定义(比如synchronized/volatile/final),就可以使用这些关键词来保证程序是按照想要的逻辑在多线程环境下正确执行。

旧有JMM的问题

问题#1:不可变(Immutable)对象并不是不可变的
不可变对象:对象的所有字段必须是final的,并且必须是基元类型或者是不可变对象的引用。比如,String对象是不可变的,语义上来说应该不需要 synchronization,但事实上是,多线程场景下有可能有竞争条件。
@JKD1.4
class String
{static final char[] charArray;static int length;static int offset;     // 表示字符串的开始位置
}

charArray数组以及length、offset可以在多个String/StringBuffer中共享。比如 String.substring()就是共享了原有String的charArray。

String s1 = "/usr/tmp";
String s2 = s1.substring(4);   // contains "/tmp"

在旧有JMM中,没有synchronization。初始化s1的时,object的构造器将length/offset初始化为0,此时其他线程可以访问这些值(这个时候使用substring明显就有问题),接下来String的构造器为length/offset赋值为需要的值。新JMM模型解决了final变量在构造器中初始化时的线程安全问题,即:final变量在初始化之前(构造函数执行完毕之前)是不允许其他线程可见的。

问题#2:volatile变量与非volatile变量的重排序
volatile变量在旧有JMM仅有的一个语义:对于一个volatile变量的读,总是能看到其它任意线程对个这个volatile变量的最后写入。即,对volatile变量的写 happends-before 其他线程对其读。
在旧有JMM中,虽然不允许volatile变量之间的重排序,但允许volatile变量与其它普通变量之间重排序。这就导致了在把volatile变量作为标志位的场景下出现问题。
Map configOptions;
char[] configText;
volatile boolean initialized = false;
. . .
// In thread A
configOptions = new HashMap();
configText = readConfigFile(fileName);
processConfigOptions(configText, configOptions);
initialized = true;
. . .
// In thread B
while (!initialized) sleep();
// use configOptions ,在旧有JMM中,这里并不保证configOptions已经初始化,因为变量顺序可能已经重排。

新JMM模型解决了这个问题,引入了新语义:volatile变量不能与非volatile变量发生重排。

新的JMM

Visibility 可见性
当ThreadA执行 val = 1 后,其它线程如何能够确保看到ThreadA的执行结果(即变量此时对其它线程的Visibility)?JMM规范中的某些定义(volatile/synchronized/final)来确保这件事情。
synchronized 与可见性
synchronized 不仅有进入一个临界区锁定的语义,同时还具有内存可见性(memory visibility)方面的意义。离开synchronized块时,cache必须flush到主内存当中;进入synchronized块时,cache失效,随后的读操作必须到主内存当中读。即,synchronized块里面的变量写操作对其他线程是可见(visible)的!
Volatile
新的JMM增强了volatile的内存语义,禁止与普通变量重排。即,threadA对一个 volatile 变量 write ,threadB读取那个变量。那么,对A可见的所有变量,必须同时对B可见。
happens-before
普通的多线程如果没有任何数据共享和交互,则指令可能会因为优化的缘故进行重排,也不会造成影响。如果线程之间有数据竞争,则需要使用synchronized(Moniter)/volatile等来保证指令的执行顺序。JMM定义了一种排序叫“happens-before”,使用该概念,JMM来解释什么叫做内存可见性。如果一个操作A的结果对另外一个操作B是内存可见的,则A happends-before B。
* 一个线程中,靠前的操作 happends-before 后续操作。
* 对monitor的unlock happends-before 后续的加锁操作。
* 对volatile变量的写操作 happends-before 后续的读。
* Thread.start()的调用操作 happends-before 线程内部操作。
* 线程的所有操作都 happends-before 后续的线程join()。

Data races —— 数据竞争
存在数据竞争,说明该程序没有进行良好的同步,没有处理好 happends-before 关系。
final变量的初始化安全
对象只有在完全初始化后,其final变量对其它线程才是可见的。这说明,final变量的初始化可以在不使用synchronization的情况下实现线程安全。这样,final变量实现了真正意义上的final(即,不-可-变),而不会在初始化过程中、后在多线程中看起来会改变。
class A
{final Map<String,String> map=null;public void A(){map = new HashMap<String,String>();map.put("key1","value1");}
}

其他线程在引用A的对象之前,JMM保证A的final变量已经被其构造函数完全初始化。也就是说,final变量的完全初始化 happends-before 其他线程对该对象的引用。即,final变量在构造函数中的初始化是线程安全的。

Refs

http://en.wikipedia.org/wiki/Java_Memory_Model
http://www.ibm.com/developerworks/library/j-jtp02244/index.html 
http://www.ibm.com/developerworks/library/j-jtp03304/  
http://www.cs.umd.edu/~pugh/java/memoryModel/ 
http://www.infoq.com/cn/articles/java-memory-model-1 
http://www.cs.umd.edu/~pugh/java/memoryModel/jsr-133-faq.html 

简述Java内存模型的由来、概念及语义相关推荐

  1. 程序员写好技术文章的几点小技巧,简述java内存模型面试

    开头 该文档在Github上收获5K+star的Java核心神技(这参数,质量多高就不用我多说了吧)非常全面,包含基础知识.Java集合.JVM.多线程并发.spring原理.微服务.Netty 与R ...

  2. 什么是Java内存模型?为什么会引发线程安全问题?

    近日,有热心市民就 "Java内存模型 " 提出质疑: 线程是否会把所有需要操作的数据全加载到内存 ​根据<我是憨包>可以看出,当事人蛋蛋(化名)目前情绪稳定,并且似乎 ...

  3. 彻底理解Java内存模型,它为什么会引发线程安全问题【吐血总结】

    近日,有热心市民就 "Java内存模型 " 提出质疑: 线程是否会把所有需要操作的数据全加载到内存 根据<我是憨包>可以看出,当事人蛋蛋(化名)目前情绪稳定,并且似乎已 ...

  4. JMM Java内存模型的概念以及happens-before原则

    详细介绍了JMM Java内存模型的概念.由来,以及happens-before原则的具体规则. Java内存模型(Java Memory Model,JMM)是java虚拟机规范定义的一组规范以及机 ...

  5. 区分 JVM 内存结构、 Java 内存模型 以及 Java 对象模型 三个概念

    本文由 简悦 SimpRead 转码, 原文地址 https://www.toutiao.com/i6732361325244056072/ 作者:Hollis 来源:公众号Hollis Java 作 ...

  6. 循序渐进:带你理解什么是Java内存模型

    近期笔者在阅读<深入理解Java虚拟机:JVM高级特性与最佳实现(第3版)>,书中提到关于Java内存模型的知识点,但是看完之后还是感觉有些模糊,便查阅一些其他相关资料.本文是笔者经过对知 ...

  7. Java高并发编程(三):Java内存模型

    1 Java内存模型的基础 在并发编程里,需要处理两个问题: 线程之间如何通信 线程之间如何同步. 通信指的是线程之间以何种机制来交换信息.在命令式编程里中,线程之间的通信机制有两种:共享内存和消息传 ...

  8. blp模型 上读下写_Java高并发编程(三):Java内存模型

    1 Java内存模型的基础 在并发编程里,需要处理两个问题: 线程之间如何通信 线程之间如何同步. 通信指的是线程之间以何种机制来交换信息.在命令式编程里中,线程之间的通信机制有两种:共享内存和消息传 ...

  9. 01.java内存模型

    文章目录 1. 简述 2. JAVA 内存模型的规则 2.1. 对线程的非共享变量不做任何处理 2.2. 线程共享变量提供同步机制 2.2.1 同步顺序 2.2.2. HAPPENS-BEFORE 参 ...

最新文章

  1. Bootstrap4.x 新增
  2. UNIX再学习 -- 网络IPC:套接字
  3. Delphi Menu Designer(菜单设计器)之一
  4. java mysql failover_mysqlfailover测试
  5. android system window,Android之属性fitsSystemWindows
  6. [Flex]Flex 3.0 and Adobe AIR 1.0正式版发布!
  7. SocketType 枚举----指定 Socket 类的实例表示的套接字的类型
  8. c++之按距离某点的距离排序
  9. Eclipse_设置JSP模板
  10. 玩转华为ENSP模拟器系列 | 配置URPF示例
  11. 数据结构(四):KD树
  12. p39最大子数组问题o(lgn)
  13. recover的用法
  14. ACwing 1018 最低通行费
  15. 小白从0学习推荐系统 ---01 推荐系统简介
  16. 派克Parker耐高低温伺服电机在汽车检测行业中的重要应用
  17. error C2248: “CObject::CObject”: 无法访问private 成员(在“CObject”类中声明)
  18. CAD文件怎么转成低版本?教你两个小妙招
  19. 【券后价9.90元】【包邮】荷叶茶冬瓜荷叶茶叶纯干玫瑰花茶袋泡花草茶包组合天然决明正品子...
  20. linux 硬盘对拷,比ghost好用

热门文章

  1. 使用python的eval()函数循环调用某些模块中的一些方法和属性
  2. 在Jetson Xavier NX安装中文输入法(googlepinyin中文输入法)
  3. guava中的Joiner
  4. LeetCode Odd Even Linked List
  5. PHP isset 函数作用
  6. [Nikon D80]Beauty
  7. VS NuGet使用
  8. POJ 3159[差分约束]
  9. SVN状态图标不显示
  10. Android布局之相对布局——RelativeLayout