一、happens-before

    happens-before是JMM最核心的概念。对于Java程序员来说,理解happens-before是理解JMM的关键。

  1.1 JMM的设计

    从JMM设计者的角度,在设计JMM时,需要考虑两个关键因素:

    1、程序员对内存模型的使用。程序员希望内存模型易于理解、易于编程。程序员希望基于一个强内存模型来编写代码。

    2、编译器和处理器对内存模型的实现。编译器和处理器希望内存模型对他们的束缚越少越好,这样它们就可以尽可能多的优化来提高性能。编译器和处理器希望实现一个弱内存模型。

    由于这连个因素互相矛盾,所以JSR-133专家组在设计JMM时的核心目标就是找到一个好的平衡点;一方面,要为程序员提供足够强的内存可见性保证;另一方面,对编译器和处理器的限制要尽可能地放松。下面让我们来看看JSR-133是如何实现这一目标的。

    double  pi=3.14;      //A

    double  r =1.0;       //B

    double  area=pi * r * r;   //C

    上面计算圆的面积的示例代码存在3个happens-before关系,如下:

    A happens-before B。

    B happens-before C。

    A happens-before C。

    在3个happens-before关系中,2和3是必需的,但1是不必要的。因此,JMM把happens-before要求禁止的重排序分为了下面两类:

    1、会改变程序执行结果的重排序。

    2、不会改变程序执行结果的重排序。

    JMM对这两种不同性质的重排序,采取了不同的策略,如下:

    1、对于会改变程序执行结果的重排序,JMM要求编译器和处理器必须禁止这种重排序。

    2、对于不会改变程序执行结果的重排序,JMM对编译器和处理器不做要求(JMM允许这种重排序)。

    JMM向程序员提供的happens-before规则能满足程序员的需求。JMM的happens-before规则不但简单易懂,而且也向程序员提供了足够强的内存可见性保证(有些内存可见性保证其实并不一定真实存在,比如上面的A happens-before B)。

    JMM对编译器和处理器的束缚已经尽可能少。JMM其实在遵守一个基本原则:只要不改变程序的执行结果(指的是单线程程序和正确同步的多线程程序),编译器和处理器怎么优化都可以。

  1.2 happens-before的定义

    1、如果一个操作happens-before另一个操作,那么第一个操作的执行结果将对第二个操作可见,而且第一个操作的执行顺序排在第二个操作之前。

    2、两个操作之间存在happens-before关系,并不意味着Java平台的具体实现必须要按照happens-before关系指定的顺序来执行。如果重排序之后的执行结果,与按happens-before关系来执行的结果一致,那么这种重排序并不非法。

    上面的1是JMM对程序员的承诺。从程序员的角度来说,可以这样理解happens-before关系:如果A happens-before B,那么Java内存模型将向程序员保证-A操作的结果将对B可见,且A的执行顺序排在B之前。注意,这只是Java内存模型向程序员做出的保证!

    上面的2是JMM对编译器和处理器重排序的约束原则。正如前面所言,JMM其实是在遵守一个基本原则:只要不改变程序的执行结果,编译器和处理器怎么优化都行。JMM这么做的原因是:程序员对于这两个操作是否真的是被重排序并不关心,程序员关心的是程序执行时的语义不能被改变,即执行结果不能被改变。因此,happens-before关系本质上和as-if-serial语义是一回事。

    as-if-serial语义保证单线程内程序的执行结果不被改变,happens-bofore关系保证正确同步的多线程程序的执行结果不被改变。

    as-if-serial语义给编写单线程程序的程序员创造了一个幻境:单线程程序是按程序的顺序来执行的。happens-before关系给编写正确同步的多线程程序的程序员创造了一个幻境:正确同步的多线程程序是按happens-before指定的顺序来执行的。

    as-if-serial语义和happens-before这么做的目的,都是为了在不改变程序执行结果的前提下,尽可能地提高程序执行的并行度。

  1.3 happens-before规则

    1、程序员规则:一个线程中的每个操作,happens-before于该线程中的任意后续操作。

    2、监视器锁规则:对一个锁的解锁,happens-before于随后对这个锁的加锁。

    3、volatile变量规则:对一个volatile域的写,happens-before于任意后续对这个volatile域的读。

    4、传递性:如果A happens-before B,且B happens-before C,那么A happens-before C。

    5、start()规则:如果线程A执行操作ThreadB.start()(启动线程B),那么A线程的ThreadB.start()操作happens-before于线程B中的任意操作。

    6、join()规则:如果线程A执行操作ThreadB.join()并成功返回,那么线程B中的任意操作happens-before于线程A从ThreadB.join()操作成功返回。

二、双重检查锁定与延迟初始化

   略

三、Java内存模型综述

  3.1 各种内存模型之间的关系

    JMM是一个语言级的内存模型,处理器内存模型是硬件级的内存模型,顺序一致性内存模型是一个理论参考模型。常见的处理器内存模型要比常见的语言级内存模型弱,处理器内存模型和语言级内存模型都要比顺序一致性内存模型弱。同处理器内存模型一样,越是追求执行性能的语言,内存模型设计的会越弱。

  3.2 JMM的内存可见性保证

    按程序类型,Java程序的内存可见性保证可以分为下列3类。

    1、单线程程序。单线程程序不会出现内存可见性问题。编译器、runtime和处理器会共同确保单线程程序的执行结果与该程序在顺序一致性模型中的执行结果相同。

    2、正确同步的多线程程序。正确同步的多线程程序的执行将具有顺序一致性(程序的执行结果与该程序在顺序一致性内存模型中的执行结果相同)。这是JMM关注的重点,JMM通过限制编译器和处理器的重排序来为程序员提供内存可见性保证。

    3、未同步/未正确同步的多线程程序。JMM为他们提供了最小安全性保障;线程执行时读取到的值,要么是之前某个线程写入的值,要么是默认值(0,null,false)。

  3.3 JSR-133对旧内存模型的修补

    JSR-133对JDK 5之前的旧内存模型的修补主要有两个:

    1、增强volatile的内存语义。旧内存模型允许volatile变量与普通变量重排序。JSR-133严格限制volatile变量与普通变量的重排序,使volatile的写-读和锁的释放-获取具有相同的内存语义。

    2、增强final的内存语义。在旧内存模型中,多次读取同一个final变量的值可能会不相同。为此,JSR-133为final增加了两个重排序规则。在保证final引用不会从构造函数内逸出的情况下,final具有了初始化安全性。

总结

    本章对Java内存模型做了比较全面的解读,有助于解决在Java并发编程中经常遇到的各种内存可见性问题。

(第三章)Java内存模型(下)的更多相关文章

  1. 第三章 Java内存模型(下)

    锁的内存语义 中所周知,锁可以让临界区互斥执行.这里将介绍锁的另一个同样重要但常常被忽视的功能:锁的内存语义 锁的释放-获取建立的happens-before关系 锁是Java并发编程中最重要的同步机 ...

  2. (第三章)Java内存模型(上)

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

  3. (第三章)Java内存模型(中)

    一.volatile的内存语义 1.1 volatile的特性 理解volatile特性的一个好办法是把对volatile变量的单个读/写,看成是使用同一个锁对这些单个读/写操作做了同步.下面通过具体 ...

  4. 第三章 Java内存模型(上)

    本章大致分为4部分: Java内存模型的基础:主要介绍内存模型相关的基本概念 Java内存模型中的顺序一致性:主要介绍重排序和顺序一致性内存模型 同步原语:主要介绍3个同步原语(synchroized ...

  5. 《深入理解Java虚拟机》-----第12章 Java内存模型与线程

    概述 多任务处理在现代计算机操作系统中几乎已是一项必备的功能了.在许多情况下,让计算机同时去做几件事情,不仅是因为计算机的运算能力强大了,还有一个很重要的原因是计算机的运算速度与它的存储和通信子系统速 ...

  6. 深入理解java虚拟机-第12章Java内存模型与线程

    第12章 Java内存模型与线程 Java内存模型  主内存与工作内存: java内存模型规定了所有的变量都在主内存中,每条线程还有自己的工作内存. 工作内存中保存了该线程使用的主内存副本拷贝,线程对 ...

  7. java并发编程实战:第十六章----Java内存模型

    一.什么是内存模型,为什么要使用它 如果缺少同步,那么将会有许多因素使得线程无法立即甚至永远看到一个线程的操作结果 编译器把变量保存在本地寄存器而不是内存中 编译器中生成的指令顺序,可以与源代码中的顺 ...

  8. 并发之初章Java内存模型

    >>>>>>博客地址<<<<<< >>>>>>首发博客<<<<< ...

  9. JVM系列(三)— Java内存模型

    我们已经了解了Java虚拟机的运行时数据区,垃圾收集相关知识,接下来学习虚拟机非常重要的部分 这就是Java内存模型与线程(第12章),这一篇,将主要讲讲内存模型 了解Java内存模型之前,先了解下计 ...

随机推荐

  1. Qt5如何设置静态编译,解决生成的可执行文件打开出错问题

    将https://yunpan.cn/cqGGURjmG2fEY  访问密码 8de5  中的压缩包Qt5-MSVC-Static-master.zip 解压到你的qt安装目录,一般就是C:\Qt下, ...

  2. addChildViewController 用法

    // // SCMyOrderViewController.m // SmartCommunity // // Created by chenhuan on 15/9/7. // Copyright ...

  3. PHP判断远程图片或文件是否存在

    PHP判断远程图片是否存在,此方法同样适用于判断远程文件是否存在,这是一种既然有效率且又准确的方法,建议采用此方法,以往使用get_headers()方法判断都是有问题的: function chec ...

  4. Python自动化运维之6、函数装饰器

    装饰器: 装饰器可以使函数执行前和执行后分别执行其他的附加功能,这种在代码运行期间动态增加功能的方式,称之为“装饰器”(Decorator),装饰器的功能非常强大.装饰器一般接受一个函数对象作为参数, ...

  5. Python自动化运维之4、格式化输出、文件对象

    Python格式化输出: Python的字符串格式化有两种方式: 百分号方式.format方式 百分号的方式相对来说比较老,而format方式则是比较先进的方式,企图替换古老的方式,目前两者并存.[P ...

  6. gcc使用笔记

    1.如何在gcc中传输宏定义? 参考如下红色部分,可以传入宏定义 gcc [-c|-S|-E] [-std=standard] [-g] [-pg] [-Olevel] [-Wwarn...] [-p ...

  7. UCOS 信号量的初值问题

    当 pend请求发出的时候信号量的值减1,当post的时候信号量的值加1,信号量的值0跟1分别是用来同步跟互斥的,什么是同步,什么是互斥呢...假设你把信号量的值设为0,有A,B连个任务,当A发出pe ...

  8. 使用开源word操作组件DocX的记录

    1.DocX简介 1.1 简介 DocX是一个在不需要安装word的情况下对word进行操作的开源轻量级.net组件,是由爱尔兰的一个叫Cathal Coffey的博士生开发出来的.DocX使得操作w ...

  9. Golang http包下FileServer的使用

    FileServer文档:https://godoc.org/net/http#FileServer 今天看到http的 Handle 方法,所以就像试试,就找到FileServer FileServ ...

  10. js 比较日期大小

    //1获取当前时间 var curTime = new Date(); //2把字符串格式转换为日期类 var startTime = new Date(Date.parse(kc.begintime ...