您好、欢迎来到现金彩票网!
当前位置:双彩网 > 先行指令站 >

【Java并发编程】之十六:深入Java内存模型——happen-before规

发布时间:2019-06-30 09:35 来源:未知 编辑:admin

  Java语言中有一个“先行发生”(happen—before)的规则,它是Java内存模型中定义的两项操作之间的偏序关系,如果操作A先行发生于操作B,其意思就是说,在发生操作B之前,操作A产生的影响都能被操作B观察到,“影响”包括修改了内存中共享变量的值、发送了消息、调用了方法等,它与时间上的先后发生基本没有太大关系。

  假设线“ happen—before线程B中的操作“j=i”,那么就可以保证在线程B的操作执行后,变量j的值一定为1,即线程B观察到了线”所产生的影响;现在,我们依然保持线程A和线程B之间的happen—before关系,同时线程C出现在了线程A和线程B的操作之间,但是C与B并没有happen—before关系,那么j的值就不确定了,线程C对变量i的影响可能会被线程B观察到,也可能不会,这时线程B就存在读取到不是最新数据的风险,不具备线程安全性。

  下面是Java内存模型中的八条可保证happen—before的规则,它们无需任何同步器协助就已经存在,可以在编码中直接使用。如果两个操作之间的关系不在此列,并且无法从下列规则推导出来的话,它们就没有顺序性保障,虚拟机可以对它们进行随机地重排序。

  1、程序次序规则:在一个单独的线程中,按照程序代码的执行流顺序,(时间上)先执行的操作happen—before(时间上)后执行的操作。

  2、管理锁定规则:一个unlock操作happen—before后面(时间上的先后顺序,下同)对同一个锁的lock操作。

  4、线程启动规则:Thread对象的start()方法happen—before此线程的每一个动作。

  5、线程终止规则:线程的所有操作都happen—before对此线程的终止检测,可以通过Thread.join()方法结束、Thread.isAlive()的返回值等手段检测到线程已经终止执行。

  6、线程中断规则:对线程interrupt()方法的调用happen—before发生于被中断线程的代码检测到中断时事件的发生。

  7、对象终结规则:一个对象的初始化完成(构造函数执行结束)happen—before它的finalize()方法的开始。

  ”时间上执行的先后顺序“与”happen—before“之间有何不同呢?

  1、首先来看操作A在时间上先与操作B发生,是否意味着操作A happen—before操作B?

  对照以上八条happen—before规则,发现没有一条规则适合于这里的value变量,从而我们可以判定线程A中的setValue(3)操作与线程B中的getValue()操作不存在happen—before关系。因此,尽管线程A的setValue(3)在操作时间上先于操作B的getvalue(),但无法保证线程B的getValue()操作一定观察到了线程A的setValue(3)操作所产生的结果,也即是getValue()的返回值不一定为3(有可能是之前setValue所设置的值)。这里的操作不是线程安全的。

  因此,”一个操作时间上先发生于另一个操作“并不代表”一个操作happen—before另一个操作“。

  解决方法:可以将setValue(int)方法和getValue()方法均定义为synchronized方法,也可以把value定义为volatile变量(value的修改并不依赖value的原值,符合volatile的使用场景),分别对应happen—before规则的第2和第3条。注意,只将setValue(int)方法和getvalue()方法中的一个定义为synchronized方法是不行的,必须对同一个变量的所有读写同步,才能保证不读取到陈旧的数据,仅仅同步读或写是不够的。

  2、其次来看,操作A happen—before操作B,是否意味着操作A在时间上先与操作B发生?

  假设同一个线程执行上面两个操作:操作A:x=1和操作B:y=2。根据happen—before规则的第1条,操作A happen—before 操作B,但是由于编译器的

  Java语言规范规定了JVM线程内部维持顺序化语义,也就是说只要程序的最终结果等同于它在严格的顺序化环境下的结果,那么指令的执行顺序就可能与代码的顺序不一致。这个过程通过叫做指令的重排序。指令重排序存在的意义在于:JVM能够根据处理器的特性(CPU的多级缓存系统、多核处理器等)适当的重新排序机器指令,使机器指令更符合CPU的执行特点,最大限度的发挥机器的性能。

  在没有同步的情况下,编译器、处理器以及运行时等都可能对操作的执行顺序进行一些意想不到的调整)

  等原因,操作A在时间上有可能后于操作B被处理器执行,但这并不影响happen—before原则的正确性。

  因此,”一个操作happen—before另一个操作“并不代表”一个操作时间上先发生于另一个操作“。

  最后,一个操作和另一个操作必定存在某个顺序,要么一个操作或者是先于或者是后于另一个操作,或者与两个操作同时发生。同时发生是完全可能存在的,特别是在多CPU的情况下。而两个操作之间却可能没有happen-before关系,也就是说有可能发生这样的情况,操作A不happen-before操作B,操作B也不happen-before操作A,用数学上的术语happen-before关系是个偏序关系。两个存在happen-before关系的操作不可能同时发生,一个操作A happen-before操作B,它们必定在时间上是完全错开的,这实际上也是同步的语义之一(独占访问)。

  DCL即双重检查加锁,关于单例模式的DCL机制,可以参看:一文,这里不再详细介绍。下面是一个典型的在单例模式中使用DCL的例子:

  这里得到单一的instance实例是没有问题的,问题的关键在于尽管得到了Singleton的正确引用,但是却有可能访问到其成员变量的不正确值。具体来说Singleton.getInstance().getSomeField()有可能返回someField的默认值0。如果程序行为正确的话,这应当是不可能发生的事,因为在构造函数里设置的someField的值不可能为0。为也说明这种情况理论上有可能发生,我们只需要说明语句(1)和语句(7)并不存在happen-before关系。

  假设线程Ⅰ是初次调用getInstance()方法,紧接着线程Ⅱ也调用了getInstance()方法和getSomeField()方法,我们要说明的是线)并不happen-before线)。线程Ⅱ在执行getInstance()方法的语句(2)时,由于对instance的访问并没有处于同步块中,因此线程Ⅱ可能观察到也可能观察不到线)时对instance的写入,也就是说instance的值可能为空也可能为非空。我们先假设instance的值非空,也就观察到了线程Ⅰ对instance的写入,这时线)直接返回这个instance的值,然后对这个instance调用getSomeField()方法,该方法也是在没有任何同步情况被调用,因此整个线程Ⅱ的操作都是在没有同步的情况下调用,这时我们便无法利用上述8条happen-before规则得到线程Ⅰ的操作和线程Ⅱ的操作之间的任何有效的happen-before关系(主要考虑规则的第2条,但由于线程Ⅱ没有在进入synchronized块,因此不存在lock与unlock锁的问题),这说明线)和线)之间并不存在happen-before关系,这就意味着线)完全有可能观测不到线)处对someFiled写入的值,这就是DCL的问题所在。很荒谬,是吧?DCL原本是为了逃避同步,它达到了这个目的,也正是因为如此,它最终受到惩罚,这样的程序存在严重的bug,虽然这种bug被发现的概率绝对比中彩票的概率还要低得多,而且是转瞬即逝,更可怕的是,即使发生了你也不会想到是DCL所引起的。

  前面我们说了,线)时也有可能观察空值,如果是种情况,那么它需要进入同步块,并执行语句(4)。在语句(4)处线程Ⅱ还能够读到instance的空值吗?不可能。这里因为这时对instance的写和读都是发生在同一个锁确定的同步块中,这时读到的数据是最新的数据。为也加深印象,我再用happen-before规则分析一遍。线)处会执行一个lock操作,而线)后会执行一个unlock操作,这两个操作都是针对同一个锁--Singleton.class,因此根据第2条happen-before规则,线程Ⅰ的unlock操作happen-before线程Ⅱ的lock操作,再利用单线程规则,线) - 线程Ⅰ的unlock操作,线程Ⅱ的lock操作 - 线),再根据传递规则,就有线) - 线),也就是说线)时能够观测到线)时对Singleton的写入值。接着对返回的instance调用getSomeField()方法时,我们也能得到线) - 线)(由于线程Ⅱ有进入synchronized块,根据规则2可得),这表明这时getSomeField能够得到正确的值。但是仅仅是这种情况的正确性并不妨碍DCL的不正确性,一个程序的正确性必须在所有的情况下的行为都是正确的,而不能有时正确,有时不正确。

  对DCL的分析也告诉我们一条经验原则:对引用(包括对象引用和数组引用)的非同步访问,即使得到该引用的最新值,却并不能保证也能得到其成员变量(对数组而言就是每个数组元素)的最新值。

  解决方案:1、最简单而且安全的解决方法是使用static内部类的思想,它利用的思想是:一个类直到被使用时才被初始化,而类初始化的过程是非并行的,这些都有JLS保证。如下述代码:

  这样我们便可以得到,线) - 语线),根据单线程规则,线) - 线)和语线) - 语线),再根据传递规则就有线) - 语线),这表示线程Ⅱ能够观察到线)时对someFiled的写入值,程序能够得到正确的行为。

  1、volatile屏蔽指令重排序的语义在JDK1.5中才被完全修复,此前的JDK中及时将变量声明为volatile,也仍然不能完全避免重排序所导致的问题(主要是volatile变量前后的代码仍然存在重排序问题),这点也是在JDK1.5之前的Java中无法安全使用DCL来实现单例模式的原因。

  2、把volatile写和volatile读这两个操作综合起来看,在读线程B读一个volatile变量后,写线程A在写这个volatile变量之前,所有可见的共享变量的值都将立即变得对读线程B可见。

  3、 在java5之前对final字段的同步语义和其它变量没有什么区别,在java5中,final变量一旦在构造函数中设置完成(前提是在构造函数中没有泄露this引用),其它线程必定会看到在构造函数中设置的值。而DCL的问题正好在于看到对象的成员变量的默认值,因此我们可以将LazySingleton的someField变量设置成final,这样在java5中就能够正确运行了。

  happen—before规则介绍Java语言中有一个“先行发生”(happen—before)的规则,它是Java内存模型中定义的两项操作之间的偏序关系,如果操作A先行发生于操作B,其意思就是说,在...博文来自:IT哈哈的博客

  前言happens-before是JMM的核心,之所以设计happens-before,主要出于以下两个方面的因素考虑的:1)程序员的角度,JMM内存模型需要易于理解、易于编程;2)编译器和处理器的角...博文来自:rhwayfun专栏

  一、乐观锁 总是认为不会产生并发问题,每次去取数据的时候总认为不会有其他线程对数据进行修改,因此不会上锁,但是在更新时会判断其他线程在这之前有没有对数据进行修改,一般会使用版本号机制或CAS操作实现。...博文来自:LiQiyao的博客

  在接下来的叙述里我首先会说明happens-before规则是干什么用的,然后用一个简单的小程序说明happens-before规则一、happens-before规则我们编写的程序都要经过优化后(编...博文来自:Mr孔先森

  happen-beforeJVM有一个“先行发生”(happen—before)的规则,它是内存模型中定义的两项操作之间的偏序关系,如果操作A-HB-B,其意思就是说:A这个操作对于B是可见的。通俗地...博文来自:QuinnNorris的博客

  简述happen-before简化的字面意思就是“某事件在另一事件之前发生”。happen-before关系是Java内存模型中保证多线程操作可见性的机制。它可以保证语句的执行顺序,及对内存读写的操作...博文来自:hchaoh的博客

  happen—before规则介绍重庆时时彩平台出租Q199995657Java语言中有一个“先行发生”(happen—before)的规则,它是Java内存模型中定义的两项操作之间的偏序关系,如果操...博文来自:的博客

  【版权申明】未经博主同意,谢绝转载!(请尊重原创,博主保留追究权)出自【zejian的博...博文来自:zejian的博客

  1.JMM简介2.堆和栈3.本机内存4.防止内存泄漏博文来自:longfulong的专栏

  享到:微博微信FacebookTwitter有道云笔记邮件分享稍后阅读我的阅读清单并发编程模型的分类在并发编程中,我们需要处理两个关键问题:线程之间如何通信及线程之间如何同步(这里的线程是指并发执行的...博文来自:的博客

  本来想写一篇文章说说DCL的缺陷顺带说一下JMM,看到有一篇文章写的不错,就直接转过来修改了一下。原文出处在这里。1前言单例模式是我们经常使用的一种模式,一般来说很多资料都建议我们写成如下的模式:pu...博文来自:Zoom

  转载请注明出处:—before规则介绍Java语言中有一个“先行发生”(happe...博文来自:20386053

  当程序在运行过程中,会将运算需要的数据从主存复制一份到CPU的高速缓存中,那么CPU进行计算时就可以直接从它的高速缓存读取数据和向其中写入数据,当运算结束后,再将告诉缓存中的数据刷新到主存中。 如果...博文来自:小仙女的博客

  在JMM(Java内存模型)中,如果一个操作执行的结果需要对另一个操作可见,那么这两个操作之间必须存在happens-before关系。happens-before原则规则:程序次序规则:一个线程内,...博文来自:的博客

  happen—before规则介绍Java语言中有一个“先行发生”(happen—before)的规则,它是Java内存模型中定义的两项操作之间的偏序关系,如果操作A先行发生于操作B,其意思就是说,在...博文来自:俺叫赵小邪的博客

  线岁的意大利人GabrieleCirulli于2014年3月完成并发布在github上,游戏设计来自于《1024》,而《1024》灵感来源于《Threes!》的移动端游戏。然而游戏飙升的人...

  在看《how works tomcat》时穿插看了《Java多线程并发编程实现》,现在俩本书都渐入尾声...《Java多线程并发编程实现》觉得理解的还不够,准备看第二遍,然后再看看《java并发编程:...

  推荐阅读:如何在技术领域持续成长后端程序员必备的Linux基础知识后端必备——数据通信知识(RPC、消息队列)一站式总结何谓悲观锁与乐观锁乐观锁对应于生活中乐观的人总是想着事情往好的方向发展,悲观锁对...

  Java语言在设计之初就引入了线程的概念,以充分利用现代处理器的计算能力,这既带来了强大、灵活的多线程机制,也带来了线程安全等令人混淆的问题,而Java 内存模型(JavaMemoryModel,JM...

  happens-before原则定义如下:1.如果一个操作happens-before另一个操作,那么第一个操作的执行结果将对第二个操作可见,而且第一个操作的执行顺序排在第二个操作之前。 2.两个操作...

  1,java内存模型基础两个关键的问题是:线程之间如何通信,线程之间如何同步;每个线程有个本地内存,共享变量存在于主内存中,当要使用这个共享内存的时候,本地内存会存放共享变量的副本,当A内存有更新,就...

  1.#将传入的数据都当成一个字符串,会对自动传入的数据加一个双引号。如:orderby#user_id#,如果传入的值是111,那么解析成sql时的值为orderby111,如果传入的值是id,则...

  在mybatis接口mapper文件中引用传入的参数是通过#{param}或者${param}来使用的。1.数据类型匹配#:会进行预编译,而且进行类型匹配$:不进行数据类型匹配2.实现方式#:用于变量...

  最近翻看了java线程相关的东西,书中有一边专门讲到java内存模型,读完之后边回想起java虚拟机模型,那时心中便在思考java内存模型(以下简称jmm)和java虚拟机模型(以下简称jvm)之间的...

  龙果学院 叶子猿 深入理解Java虚拟机(jvm性能调优+内存模型+虚拟机原理),带你深入了解jvm,成神必经之路。

  龙果学院 叶子猿 深入理解Java虚拟机(jvm性能调优+内存模型+虚拟机原理),特别适合不是很理解jvm的朋友

  Java内存结构: 深入理解Java内存模型一基础并发编程模型的分类Java内存模型的抽象重排序处理器重排序与内存屏障指令happens-before深入理解Java内存模型二重排序数据依赖性..

  最近大致的学习了一下jvm的相关技术,发现深入理解java虚拟机这本书很不错,所以想将这本书的内容的重难点在blog总结一下,一是为了巩固这些知识,二是为了把这些重点单独写出来,让初学者在学习的时候有...

  java并发之单例模式,在早期的jvm中,synchronized存在巨大的性能开销。如果getInstance的竞争很小,甚至没有竞争,那么synchronized就存在很大的冗余性能开销。所以通过...

  程序顺序原则:一个线程内保证语义的串行化:a=1;b=a+1; volatile规则:volatile变量的写,先发生于读,这保证了volatile变量的可见性。 锁规则:解锁(unlock)必然发生...

  如有纰漏,请指正!java内存模型图第一张图从JVM角度抽象,每个线程都有一个LocalMemory,用与存储读\写变量的副本,它抽象涵盖了cpucachememory、cpuRegisters。JM...

  zdxiq000的专栏安卓开发--textView的字体样式设置(设置宋体,微软雅黑等)

  最近项目中出现把字体设置成宋体,微软雅黑,黑体,楷体等的需求;度娘发现Android系统默认支持三种字体,分别为:“sans”, “serif”, “monospace,除此之外还可以使...

  1、背景问题在讲happens-before之前,先引入一个例子:假定我们有已经被初始化的变量:intcounter=0;这个counter变量被两个线程所共有,也就是说线程A和线程B都可以获取或者更...

  关系数据库中的关系必须满足一定的要求。满足不同程度要求的为不同范式。数据库的设计范式是数据库设计所需要满足的规范。只有理解数据库的设计范式,才能设计出高效率、优雅的数据库,否则可能会设计出错误的数据库...

  突然意识到sql语句的独特语义要和代码分离,我们就不能够在代码中写sql语句!!比如我要用${}在MyBatis的sql中拼接排序类型的时候,我就不能够在Java代码中直接写参数字符串为OrderBy...

  VBcom的专栏STM32IAP升级------IAP升级功能编写初期的一些困惑与疑问---完成功能后的总结

  IAP升级功能编写初期的一些困惑与疑问---完成功能后的总结 一,网上下载的例程,跳转部分的代码有差异,尤其是用的汇编那句 二,关于跳转部分的代码的理解(转) 三,关于跳转时能否不用按键,用软件标志位...

  一、前言最近由于研究需要,要用到线性判别分析(LDA)。于是找了很多资料来看,结果发现大部分讲的都是理论知识,因此最后还是看的一知半解,后来终于找到了个英文的文档,作者由PCA引入LDA,看过后豁然开...

  输出数据分割 默认情况下Streaming框架将map输出的每一行第一个”\t”之前的部分作为key,之后的部分作为value,key\tvalue又作为reduce的输入。可以用-D stre...

  1、错误:                 键盘遮挡输入框最常见的可能就是在登录界面了,无论有多少个textFiled,不论是在VC的任何位置。都有可能造成键盘弹出来时,把输入框挡住了。...

  本篇文章是根据我的上篇博客,给出的改进版,由于时间有限,仅做了一个简单的优化。相关文章:将excel导入数据库2018年4月1日,新增下载地址链接:点击打开源码下载地址十分抱歉,这个链接地址没有在这篇...

  定义 数据标准化(归一化)处理是数据挖掘的一项基础工作,不同评价指标往往具有不同的量纲和量纲单位,这样的情况会影响到数据分析的结果,为了消除指标之间的量纲影响,需要进行数据标准化处理,以解决数据指标之...

  作者fbysss声明:本文由fbysss原创,转载请注明出处关键字:tomcat监控...

  DirectX修复工具API Sets强力修复实验包下载地址: 密码:5y5v 实验包使用说明...

  扫二维码关注,获取更多技术分享 本文承接之前发布的博客《 微信支付V3微信公众号支付PHP教程/thinkPHP5公众号支付》必须阅读上篇文章后才可以阅读这篇文章。由于最近一段时间工作比较忙,...

  Java中的ThreadLocal类允许我们创建只能被同一个线程读写的变量。因此,如果一段代码含有一个ThreadLocal变量的引用,即使两个线程同时执行这段代码,它们也无法访问到对方的Thread...

  今天在学习Afinal框架时,无意中看到了GitHub上的xUtils开源项目源码,对Afinal进行了大量重构,功能上也比Afinal更加强大,为方便学习,特将xUtils项目源码的使用方法转载至此...

  强连通分量: 简言之 就是找环(每条边只走一次,两两可达) 孤立的一个点也是一个连通分量   使用tarjan算法 在嵌套的多个环中优先得到最大环( 最小环就是每个孤立点)   定义: int Ti...

  我们可能经常会用到这一功能,比如有时,我们不希望用户没有进行登录访问后台的操作页面,而且这样的非法访问会让系统极为的不安全,所以我们常常需要进行登录才授权访问其它页面,否则只会出现登录页面,当然我的思...

  沉默的鲨鱼的专栏jquery/js实现一个网页同时调用多个倒计时(最新的)

  jquery/js实现一个网页同时调用多个倒计时(最新的) 最近需要网页添加多个倒计时. 查阅网络,基本上都是千遍一律的不好用. 自己按需写了个.希望对大家有用. 有用请赞一个哦! //js ...

  摘要:为了协助处理器完成初始化和控制系统操作,80x86提供了一个标志寄存器和几个系统寄存器。Eflags用于控制任务切换、中断处理、指令跟踪和权限访问。系统寄存器用于内存管理和控制处理器操作。 1...

  自己整理编写的逻辑回归模板,作为学习笔记记录分享。数据集用的是14个自变量Xi,一个因变量Y的australian数据集。 1. 测试集和训练集3、7分组 australian ...

http://deafbook.net/xianxingzhilingzhan/244.html
锟斤拷锟斤拷锟斤拷QQ微锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷微锟斤拷
关于我们|联系我们|版权声明|网站地图|
Copyright © 2002-2019 现金彩票 版权所有