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

 
 

简述Java内存模型的由来、概念及语义的更多相关文章

  1. Java并发编程(二):JAVA内存模型与同步规则

    一.Java内存模型(JMM) 它描述的是一组规则或规范,通过这组规范定义了程序中各个变量(包括实例字段,静态字段和构成数组对象的元素)的访问方式.一个线程如何和何时能看到其他线程共享变量的值,以及在 ...

  2. 【Java并发系列】--Java内存模型

    Java内存模型 1 基本概念 程序:代码,完成某一个任务的代码序列(静态概念) 进程:程序在某些数据上的一次运行(动态) 线程:一个进程有一个或多个线程组成(占有资源的独立单元) 2 JVM与线程 ...

  3. Java内存 模型理解

    概述 在正式讲Java内存模型之前,我们先了解一些物理计算机并发问题,然后一点点的引出Java内存模型的由来. 多任务处理在现在计算机操作系统中几乎是一项必备的功能.这不单是因为计算机计算能力强大,更 ...

  4. 并发与高并发(二)-JAVA内存模型

    一.java内存模型(JMM)-同步操作与规则 它描述的是一组规则或规范,通过这组规范定义了程序中各个变量(包括实例字段,静态字段和构成数组对象的元素)的访问方式.一个线程如何和何时能看到其他线程共享 ...

  5. 深入浅出Java内存模型

    面试官:我记得上一次已经问过了为什么要有Java内存模型 面试官:我记得你的最终答案是:Java为了屏蔽硬件和操作系统访问内存的各种差异,提出了「Java内存模型」的规范,保证了Java程序在各种平台 ...

  6. 深入理解 Java 内存模型(一)- 内存模型介绍

    深入理解 Java 内存模型(一)- 内存模型介绍 深入理解 Java 内存模型(二)- happens-before 规则 深入理解 Java 内存模型(三)- volatile 语义 深入理解 J ...

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

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

  8. Java内存模型的基础

    Java内存模型的基础 并发编程模型的两个关键问题 在并发编程中,需要处理两个关键问题:线程之间如何通信及线程之间如何同步(这里的线程是指并发执行的活动实体).通信是指线程之间以何种机制来交换信息.在 ...

  9. (一)、Java内存模型

    简述 Java虚拟机规范中试图定义一种Java内存模型(Java Memory Model,JMM),来屏蔽掉各种硬件和操作系统的内存访问差异,以实现让Java程序在各种平台下都能达到一致的内存访问效 ...

随机推荐

  1. SAS文档:简单的随机点名器

    本次实验,我们设计了一个简单的随机点名系统,下面我来介绍一下它的SRS文档. 1.功能需求: 1.1 模块1 在此模块中,我们设置了RandomName类,创建一个随机点名器,里面加入了所在课程的名单 ...

  2. 你必须了解的基础的 Linux 网络命令

    Linux 基础网络命令列表 我在计算机网络课程上使用 FreeBSD,不过这些 UNIX 命令应该也能在 Linux 上同样工作. 连通性 ping <host>:发送 ICMP ech ...

  3. zz---Tomcat服务器下部署项目几种方式

    http://blog.sina.com.cn/s/blog_550281c60101hvrs.html 一.静态部署1.直接将web项目文件件拷贝到webapps 目录中     Tomcat的We ...

  4. PHP 使用CURL

    CURL是一个非常强大的开源库,支持很多协议,包括HTTP.FTP.TELNET等,我们使用它来发送HTTP请求.它给我 们带来的好处是可以通过灵活的选项设置不同的HTTP协议参数,并且支持HTTPS ...

  5. LINQ to SQL大全

    LINQ to SQL语句 (1)之Where Where操作 适用场景:实现过滤,查询等功能. 说明:与SQL命令中的Where作用相似,都是起到范围限定也就是过滤作用的,而判断条件就是它后面所接的 ...

  6. PhoneJS - HTML5 JavaScript 移动开发框架

    大伙儿都知道有很多基于HTML5的移动应用框架.下一代开发工具将帮助开发者远离那些难学和让人费劲的原生SDK语言,如Objective-C,Java等.大家都知道,HTML5代表着交叉平台如移动应用程 ...

  7. C#函数式编程之递归调用

    关于递归相信大家已经熟悉的不能再熟悉了,所以笔者在这里就不多费口舌,不懂的读者们可以在博客园中找到很多与之相关的博客.下面我们直接切入正题,开始介绍尾递归. 尾递归 普通递归和尾递归如果仅仅只是从代码 ...

  8. Hadoop2.4.x 实例测试 WordCount程序

     在实例测试前先确保hadoop 启动正确 Master.Hadoop: word 1[hadoop@Master input]$ jps6736 Jps6036 NameNode4697 Secon ...

  9. [JAVA] java_实例 获得系统字体

    这个代码可以帮助理解java是如何获取系统字体并设置文字字体: import java.awt.*; import java.awt.event.*; import javax.swing.JComb ...

  10. [51单片机] TFT2.4彩屏1 [文字显示 画矩形]

    >_<:因为要驱动彩屏,所以这里采用STC90C516RD+单片机[51的89C52也行,就是可能内存在接下来的实验中可能会内存不够],晶振尽量采用高一点的,这里采用24MHz的. > ...