并发编程系列之Final域的内存语义

本人花费半年的时间总结的《Java面试指南》已拿腾讯等大厂offer,已开源在github ,欢迎star!

转载声明:转载请注明出处,本技术博客是本人原创文章

本文GitHub https://github.com/OUYANGSIHAI/JavaInterview 已收录,这是我花了6个月总结的一线大厂Java面试总结,本人已拿大厂offer,欢迎star

原文链接:blog.ouyangsihai.cn >> 并发编程系列之Final域的内存语义

前言

上节我们讲了锁的内存语义,在同步原语中我们已经讲了两个,今天再来介绍另一个同步原语Final域,了解下final域的内存语义以及重排序规则在处理器中又是如何实现的,并结合前面的volatile和锁,大家可以进行对比下,OK,开始我们今天的并发之旅吧。

final域的重排序规则

对于final域,编译器和处理器需要遵循两个重排序规则:

  • 对一个构造函数内final域的写入,与后续把这个构造对象的引用赋值给一个引用变量,这2个操作之间是不能重排序的,相当于对一个final域的写和读不能重排序;
  • 对一个final域对象的引用第一次读,和后续初次读这个final域本身,这2个操作之间不能重排序,相当于第一次读final域引用和final域不能重排序;
  • 对一个final域对象的引用第一次读,和后续初次读这个final域本身,这2个操作之间不能重排序,相当于第一次读final域引用和final域不能重排序;

    final域写的重排序规则

    禁止把final域的写重排序到构造函数之外,这个规则包含下面2个方面的实现:

  • JMM禁止编译器把final域的写重排序到构造函数之外;
  • 编译器会在final域的写入之后,构造函数return之前,插入一个StoreStore屏障。这个屏障禁止处理器把final域的写重排序到构造函数之外;
  • 此外,如果final域本身为引用类型时情况会有所不同,当final域为引用类型时,写final域重排序规则增加了一个实现:在构造函数内对一个final引用的对象的成员域的写入,与随后在构造函数外把这个被构造对象的引用赋值给一个引用变量,这两个操作之间不能重排序;

  • 编译器会在final域的写入之后,构造函数return之前,插入一个StoreStore屏障。这个屏障禁止处理器把final域的写重排序到构造函数之外;

    final域写重排序规则可以保证:在对象引用被任意线程可见之前,对象final域已经被正确的初始化结束,也就是说,任意一个线程在读final域对象引用之前,这个对象一定是初始化完毕的状态(其实还需要一点保证,对象引用不能在构造函数中“逸出”,如逸出了,很有可能发生还未被初始化结束的对象引用被赋值给其他变量),而普通域对象则不一定能保证。

    final域读的重排序规则

    在一个线程中,初次读对象的引用与初次读这个对象本身的final域(间接依赖),JMM禁止重排序这两个操作,编译器会在读final域的操作前面加一个LoadLoad屏障

    final域读重排序规则可以保证:在读一个对象的final域之前,一定先读包含这个final域的对象的引用,如果一个final对象的引用不为null,那么该final域一定已经被初始化。

    final域内存语义在处理器中的实现

    上面提到,写final域的重排序规则要求编译器在final域的写之后,构造函数return之前插入一个StoreStore屏障,读final域重排序规则要求编译器在读final域的操作前面插入一个LoadLoad屏障;

    但是由于处理器(以X86为例)不会对写-写和存在间接依赖关系的操作做重排序,所以这两种操作的屏障在处理器中都会被省略掉。

    JSR-133对final语义的增强

    旧的内存模型中,有个很严重的缺陷就是线程可能读到的final域的值会改变,就是说线程可能先看到一个final域对象的未初始值的默认值为0,然后后面读取到的是初始化之后的值1,为了避免这个问题,JSR-133对final语义做了如下的增强:

    通过为final域增加写和读重排序规则,可以为Java程序员提供初始化安全保证:只要对象是正确构造的(被构造对象的引用在构造函数中没有“逸出”),那么不需要使用同步(指lock和volatile的使用)就可以保证任意线程都能看到这个final域在构造函数中被初始化之后的值。

    并发编程系列之Final域的内存语义

    相关文章:

    原文始发于微信公众号(Justin的后端书架):

    本人花费半年的时间总结的《Java面试指南》已拿腾讯等大厂offer,已开源在github ,欢迎star!

    转载声明:转载请注明出处,本技术博客是本人原创文章

    本文GitHub https://github.com/OUYANGSIHAI/JavaInterview 已收录,这是我花了6个月总结的一线大厂Java面试总结,本人已拿大厂offer,欢迎star

    原文链接:blog.ouyangsihai.cn >> 并发编程系列之Final域的内存语义


     上一篇
    并发编程系列之线程简介 并发编程系列之线程简介
    前言 前几天我们把Java内存模型介绍了下,大家对JMM也有所认识了,从今天我们就开始走进一个我们天天挂在嘴边,听在耳边的东西:线程,对于线程相信大家都不会陌生,当然也有很多小伙伴在开发中或多或少的使用到线程,即使你没有使用过,但是并不
    2021-04-05
    下一篇 
    并发编程系列之volatile内存语义 并发编程系列之volatile内存语义
    前言 前面介绍顺序一致性模型时,我们提到了程序如果正确的同步就会具备顺序一致性,这里所说的同步泛指广义上的同步,其中包括就包括同步原语volatile,那么volatile声明的变量为什么就能保证同步呢?这又是如何实现的呢?今天就让我们一起
    2021-04-05