1.final的具体使用场景
final能够修改变量,方法和类。
1.1修饰变量
在java中变量可以分为成员变量和局部变量
1.1.1修饰成员变量
每个类中的成员变量可以分为类变量(static修饰的变量)和实例变量。这两种变量赋初值的时间是不同的。类变量可以在声明的时候和静态代码块中赋初值。而实例变量可以在声明的时候赋初值,在非静态初始化块中以及构造器中赋初值。当final变量未初始化时系统不会进行隐式初始化,会报错。
1.1.2修饰局部变量
final局部变量由程序员进行显示初始化,如果final局部变量已经进行了初始化,则后面就不能再次进行更改,如果final变量未进行初始化,可以进行赋值,当且仅有一次赋值,一旦赋值之后再次赋值就会报错。
final基本数据类型和final引用数据类型
如果final修饰的是一个基本数据类型,那么一旦初始化,就不能更改。
如果final修饰的是引用数据类型,它保存的仅仅是一个引用,final只保证这个引用类型变量所引用的地址不会改变,即一直引用这个对象,但是这个对象的属性可以改变。
宏变量
利用final变量的不可更改性,在满足以下三个条件时,变量就成为一个宏变量,即常量
1.利用final修饰符修饰。
2.在定义该fanal变量时就指定了初值。
3.该初始化值在编译时就能够唯一确定。
1.2修饰方法
重写?(不能)
当父类的方法被final修饰的时候,子类不能重写父类的该方法,比如Object中,getClass方法就是final,我们不能重写该方法,但是hashCode方法就不是final,我们就可以重写。
重载?(能)
被final修饰的方法是可以重载的。
1.3修饰类
当一个类被final修饰时,表明该类是不能被子类继承的。
2.多线程中的final
上面我们聊的final使用,应该属于Java基础层面的,当理解这些后我们就真的算是掌握了final吗?有考虑过final在多线程并发的情况吗?在java内存模型中我们知道java内存模型为了能让处理器和编译器底层发挥他们的最大优势,对底层的约束就很少,也就是说针对底层来说java内存模型就是一弱内存数据模型。同时,处理器和编译为了性能优化会对指令序列有编译器和处理器重排序。那么,在多线程情况下,final会进行怎样的重排序?会导致线程安全的问题吗?下面,就来看看final的重排序。
2.1final域重排序规则
2.1.1final域为基本数据类型
public class FinalDemo {
private int a; //普通域
private final int b; //final域
private static FinalDemo finalDemo;
public FinalDemo() {
a = 1; // 1. 写普通域
b = 2; // 2. 写final域
}
public static void writer() {
finalDemo = new FinalDemo();
}
public static void reader() {
FinalDemo demo = finalDemo; // 3.读对象引用
int a = demo.a; //4.读普通域
int b = demo.b; //5.读final域
}
}
假设线程A正在执行writer方法,线程B执行reader方法。
写final域重排序规则
禁止对final域的写重排序到构造函数之外,这个规则的实现主要包括两个方面:
1.JMM禁止编译器把final域的写重排序到构造函数之外。
2.编译器会在final域写之后,构造函数return之前,插入一个storestore内存屏障。这个内存屏障可以禁止处理器把final域写重排序到构造函数之外。
因为a和b没有数据依赖性,普通变量a可能被重排序到构造函数之外,线程B就有可能读到的是普通变量a没有初始化之前的值。而final域变量b,根据重排序规则,会禁止final修饰的变量重排序到构造函数之外,所以线程B能够正确读到b的值。
读final域重排序规则
在一个线程中,初次读对象引用和初次读该对象包含的包含的final域,JMM会禁止这两个操作的重排序
可以确保在读一个对象的final域之前,一定会先读包含这个final域对象的引用
2.1.2 final域为引用数据类型
对final修饰的对象的成员域写操作
对final修饰的变量的成员域读操作
在构造函数内对一个final修饰的对象的成员域的写入,与随后在构造函数之外把这个被构造的对象的引用赋给一个引用变量,这两个操作是不能被重排序的
示例代码:
public class FinalReferenceDemo {
final int[] arrays;
private FinalReferenceDemo finalReferenceDemo;
public FinalReferenceDemo() {
arrays = new int[1]; //1
arrays[0] = 1; //2
}
public void writerOne() {
finalReferenceDemo = new FinalReferenceDemo(); //3
}
public void writerTwo() {
arrays[0] = 2; //4
}
public void reader() {
if (finalReferenceDemo != null) { //5
int temp = finalReferenceDemo.arrays[0]; //6
}
}
}
由于对final域的写禁止重排序到构造函数之外,因此1和3不能重排序
由于一个final域的引用对象的成员域写入不能与随后将这个被构造出来的对象赋给引用变量重排序,因此2和3不能重排序。
网友评论