Java内存模型-final域的内存语义

一 引言

  说到final你肯定知道它是Java中的关键字,那么它所在Java中的作用你知道吗?不知道的话,请前往这篇了解下https://www.cnblogs.com/yuanfy008/p/8021673.html

  今天我们来说说final域在JMM中的内存语义。

二 final域的重排序规则

  开门见山,对于final域,编译器和处理器一定要遵守两个重排序规则(JSR-133才增强了final域):

  1)在构造函数内对一个final域的写入,与随后把这个被构造对象的引用赋值给一个引用变量,这个两个操作不能被重排序。

  2)初次读一个包含final域的对象的引用,与随后初次读这个final域,这两个操作之间不能重排序。

  下面我们通过案例来说明这两点(假设线程1执行writer(),随后另一个线程执行reader()方法):

public class FinalExample {
    static volatile boolean flag = true;
    int i = 0;
    final int j;
    static FinalExample obj;

    public FinalExample() { // 构造函数
        i = 1;              // 写普通域
        j = 2;              // 写final域
    }

    public static void writer() { // 线程1写入
        obj = new FinalExample();
    }

    public static void reader() { // 线程2读取
        FinalExample example = obj; // 读对象引用
        System.out.println(example.i); // 读普通域
        System.out.println(example.j); // 读final域
    }
}

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

  1)JMM禁止编译器吧final域的写重排序到构造函数之外。

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

  所以线程1执行顺序如下图(其中写普通域的顺序无法保证,理论上是存在下面三种情况的,要想验证普通域是否有重排序的结果有点难,因为无法保证线程1把普通域重排序后,线程2能够读取它之前的0值):

  读final域的重排序规则是:在一个线程中,初次读这个对象引用与初次读该对象包含的final域,JMM禁止处理器重排序这两个操作。其中编译器会在读final域操作的前面插入一个LoadLoad屏障。由于插入了loadLoad屏障,读普通域i的操作是不会重排序到读final域,但是不保证它会重排到读对象引用这个操作的前面。所以线程2的一个执行顺序就能想象到了,这里就不画线程2的执行顺序图了。 

三 final域为引用类型

  如果finaly域为引用类型,JMM中是怎么处理的呢?对于引用类型,写final域的重排序规则对编译器和处理器增加了如下约束:在构造函数内对一个final引用的对象的成员域的写入,与随后在构造函数外把这个被构造对象的引用赋值给一个引用变量,这两个操作之间不能重排序。 

       以上及其以上都要注意:只针对于构造函数方法内。另外要想以上规则确保,还需要一个条件:在构造函数内部,不能让这个被构造对象的引用被其他线程可见,也就是对应引用不能再构造函数中“逸出”。如下案例:

  

class FinalReferenceEscapeExample {
    final int i;
    static FinalReferenceEscapeExample obj;

    public FinalReferenceEscapeExample() {
        i = 1;      // 1
        obj = this; // 2 this引用逸出
    }

    public static void writer() { // 线程1
        new FinalReferenceEscapeExample();
    }

    public static void reader() { // 线程2
        if (obj != null) {
            System.out.println(obj.i);
        }
    }
}

  上面程序,第一步写final域与第二步是不保证重排序的。所以当第一步与第二步重排之后,线程1执行完这步(obj = this)后,时间片分给第二个线程执行,那么线程2将会获取final域初始化之前的值,这肯定就违背了程序的初衷。

  

原文地址:https://www.cnblogs.com/yuanfy008/p/9349275.html

时间: 2024-11-05 12:11:27

Java内存模型-final域的内存语义的相关文章

java线程内存模型,线程、工作内存、主内存

转自:http://rainyear.iteye.com/blog/1734311 java线程内存模型 线程.工作内存.主内存三者之间的交互关系图: key edeas 所有线程共享主内存 每个线程有自己的工作内存 refreshing local memory to/from main memory must  comply to JMM rules 产生线程安全的原因 线程的working memory是cpu的寄存器和高速缓存的抽象描述:现在的计算机,cpu在计算的时候,并不总是从内存读

Java并发编程之final域的内存语义

一.final域的重排序规则 对于final域,编译器和处理器要遵循两个重拍序规则: 1.在构造函数内对一个final域的写入,与随后把这个被构造对象的引用赋值给一个引用变量,这两个操作之间不能重排序. 2.初次读一个包含final域的对象的应用,与随后初次读这个final域,这两个操作之间不能重排序 下面通过一个示例来分别说明这两个规则: public class FinalTest { int i;//普通变量 final int j; static FinalTest obj; publi

Java并发编程原理与实战四十四:final域的内存语义

一.final域的重排序规则 对于final域,编译器和处理器要遵循两个重拍序规则: 1.在构造函数内对一个final域的写入,与随后把这个被构造对象的引用赋值给一个引用变量,这两个操作之间不能重排序. 2.初次读一个包含final域的对象的应用,与随后初次读这个final域,这两个操作之间不能重排序 下面通过一个示例来分别说明这两个规则: public class FinalTest { int i;//普通变量 final int j; static FinalTest obj; publi

java内存模型-final

与前面介绍的锁和 volatile 相比较,对 final 域的读和写更像是普通的变量访问.对于final 域,编译器和处理器要遵守两个重排序规则: 在构造函数内对一个 final 域的写入,与随后把这个被构造对象的引用赋值给一个引用变量,这两个操作之间不能重排序. 初次读一个包含 final 域的对象的引用,与随后初次读这个 final 域,这两个操作之间不能重排序. 下面,我们通过一些示例性的代码来分别说明这两个规则: public class FinalExample { int i; /

JVM内存模型及String对象内存分配

昨天看了一篇关于<Java后端程序员1年工作经验总结>的文章,其中有一段关于String和StringBuffer的描述,对于执行结果仍然把握不准,趁此机会也总结了下JVM内存模型. 1.JVM运行时数据区域 关于JVM内存模型之前也了解过一些,也是看过就忘,好记性比如烂笔头,记下来吧.参考此文章http://chenzhou123520.iteye.com/blog/1585224 图1 JVM运行时数据区域 (1).程序计数器(Program Counter Register): 程序计数

jvm内存模型-回收算法-和内存分配以及jdk、jre、jvm是什么关系(阿里,美团,京东面试题)

1.什么是jvm?(1)jvm是一种用于计算设备的规范,它是一个虚构出来的机器,是通过在实际的计算机上仿真模拟各种功能实现的.(2)jvm包含一套字节码指令集,一组寄存器,一个栈,一个垃圾回收堆和一个存储方法域.(3)JVM屏蔽了与具体操作系统平台相关的信息,使Java程序只需生成在Java虚拟机上运行的目标代码(字节码),就可以在多种平台上不加修改地运行.JVM在执行字节码时,实际上最终还是把字节码解释成具体平台上的机器指令执行. 2.jdk.jre.jvm是什么关系?(1)JRE(Java

JVM内存模型及对象在内存中初始化的过程

JVM内存模型 Java虚拟机所管理的内存区域,也称为运行时数据区,分为以下几个运行时数据区,如图所示 程序计数器:当前程序所执行字节码的行号指示器 程序计数器(Program Counter Register)是一块较小的内存空间,可以看作是当前线程所执行的字节码的行号指示器.在虚拟机的概念模型里,字节码解释器工作时就是通过改变这个计数器的值来选 取下一条需要执行的字节码指令,分支.循环.跳转.异常处理.线程恢复等基础功能都需 要依赖这个计数器来完成. Java虚拟机的多线程是是通过线程轮流切

【java解惑】final域变量初始化顺序

如下所示代码: public class Example049 { private final int overtime; public static final Example049 INSTANCE = new Example049();//1 private static final int CURRENT_YEAR = Calendar.getInstance().get( Calendar.YEAR);//2 private Example049() { overtime = CURR

java并发学习--第十章 java内存模型的内存语义

一.锁的内存语义 所为的java内存模型的内存语义指的就是在JVM中的实现原则. 锁的内存语义:锁除了让临界区互斥执行外,还可以让释放锁的线程向获取同一个锁的线程发送消息. 我们把上面这句话再整理下: 当线程释放锁时,JMM会把该线程对应的本地内存中的共享变量刷新到主内存中. 当线程获取锁时,JMM会把该线程对应的本地内存置为无效.从而使得被监视器保护的临界区代码必须要从主内存中去读取共享变量. 锁的内存语义实现: synchronized.ReentrantLock: 二.volatile内存