final

返回主目录

final修饰变量

  final成员变量表示常量,只能被赋值一次,赋值后值不再改变。

  当final修饰一个原生数据类型时,表示该原生数据类型的值不能发生变化;

  如果final修饰一个引用类型时,表示该引用类型不能再指向其他对象了,但该引用所指向的对象的内容是可以发生变化的。

  本质上是一回事,因为引用的值是一个地址,final要求值,即地址的值不发生变化。

  final修饰一个成员变量(属性),必须要显示初始化。

  final修饰一个原生数据类型时,这里有两种初始化方式一种是在变量声明的时候初始化第二种方法是在声明变量的时候不赋初值,但是要在这个变量所在的类的所有的构造函数中对这个变量赋初值。

//

可以做个试验,两种方式都不使用eclipse 都报错!如何是第一种方式初始化的,类申明属性时就初始化了,对所有线程可见了。不存在重排序(个人理解)

//

  当函数的参数类型声明为final时,说明该参数是只读型的。

JMM 详细介绍参考下文。

 http://ifeve.com/java-memory-model/

 存在重排序问题只能是第二种初始化。

写final域的重排序可以确保:在对象引用为任意线程可见之前,对象的final域已经被正确初始化过了,而普通域不具有这个保障。

实现:

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

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

特殊案例(代码参考上面的连接里的代码):

读final域的重排序规则可以确保:在读一个对象的final域之前,一定会先读包含这个final域的对象的引用。在这个示例程序中,如果该引用不为null,那么引用对象的final域一定已经被A线程初始化过了。

现在我们假设写线程A没有发生任何重排序,同时程序在不遵守间接依赖的处理器上执行,下面是一种可能的执行时序:

在下图中,读对象的普通域的操作被处理器重排序到读对象引用之前。读普通域时,该域还没有被写线程A写入,这是一个错误的读取操作。而读final域的重排序规则会把读对象final域的操作“限定”在读对象引用之后,此时该final域已经被A线程初始化过了,这是一个正确的读取操作。

final域为引用对象

要求:在构造函数内对一个final引用对象成员进行赋值,与随后在构造函数外把这个被构造对象的引用赋值给一个引用变量,这两个操作之间不能重排序。

package com.qdb.thinkv.thread.base;

public class FinalRefExample {
    final int[] intArray;
    
    static FinalRefExample obj;
    
    //构造函数
    public FinalRefExample(){
        //操作1
        intArray=new int[1];
        //操作2
        intArray[0]=1;
    }
    
    //写线程A执行
    public static void writer() {
        //操作3
        obj=new FinalRefExample();
    }
    //写线程B执行
    public static void writerone() {
        //操作4
        obj.intArray[0]=2;
    }
    //读线程执行
    public static void reader(){
        //操作5
        if(obj!=null){
            //操作6
            int temp1=obj.intArray[0];
        }
    }
    
    
    
}

这里假设一种情况

线程A执行完成以后执行线程B执行完成以后执行线程C

A操作对C可见,B操作对C不可见,可能出现这样的一种情况

 如果想要求确保线程C看到线程B对数组元素的写入,写线程B和读线程C之间需要使用同步原语(lock 或 volatile)来确保内存可见性。

为什么final引用不能从构造函数内逸出

package com.qdb.thinkv.thread.base;

public class FinalReferenceEscapeExample {
    final int i;
    
    static FinalReferenceEscapeExample obj;
    
    //构造函数
    public FinalReferenceEscapeExample(){
        //操作1
        i=1;
        //操作2
        obj=this;
    }
    
    //写线程A执行
    public static void writer() {
        //操作3
        obj=new FinalReferenceEscapeExample();
    }

    //读线程执行
    public static void reader(){
        //操作5
        if(obj!=null){
            //操作6
            int temp1=obj.i;
        }
    }
    
    public static void main(String[] args) {
        
    }
    
}

因为操作1和操作2之间可能重排序(疑惑点。可能吗?根据“程序顺序执行”,一个线程中的每一个操作 happenbefore于该线程中的人任意后续操作 )

Java并发编程——this引用逸出("this" Escape)

Java 事件 基础

从上图可以看出:在构造函数返回前,被构造对象的引用不能为其他线程所见,因为此时的final域可能没有被初始化。在构造函数返回后,任意线程都将保证能看到final域正确初始化之后的值。

原文地址:https://www.cnblogs.com/tianzhiyun/p/9339686.html