final基础使用
修饰类:当final关键字修饰类时,该类无法被继承,即该类无法拥有子类。final类中的所有方法都隐式为final,因为无法覆盖他们。
修饰方法:代表这个方法不可以被子类的方法重写。由于private方法是无法被继承重写的,因此private方法是隐式的final。
修饰引用变量:无法更改引用变量所指向的对象,但对象中的属性仍可以变化。
修饰基础变量:基础变量赋值一次后不可变。
所有的final修饰的字段都是编译期常量吗?
编译期常量:程序在编译时就能确定这个常量的具体值。
可能是也可能不是。
static final:
一个既是static又是final 的字段只占据一段不能改变的存储空间,它必须在定义的时候进行赋值,否则编译器将不予通过。
final域重排序规则(final线程安全实现原理)
写final域重排序规则:禁止对final域的写重排序到构造函数之外。
假设线程A在执行writer()方法,线程B执行reader()方法。
由于a,b之间没有数据依赖性,普通域(普通变量)a可能会被重排序到构造函数之外,线程B就有可能读到的是普通变量a初始化之前的值(零值),这样就可能出现错误。而final域变量b,根据重排序规则,会禁止final修饰的变量b重排序到构造函数之外,从而b能够正确赋值,线程B就能够读到final变量初始化后的值。
因此,写final域的重排序规则可以确保:在对象引用为任意线程可见之前,对象的final域已经被正确初始化过了,而普通域就不具有这个保障。
读final域重排序规则:在一个线程中,初次读对象引用和初次读该对象包含的final域,JMM会禁止这两个操作的重排序。
假设线程A写过程没有重排序,那么线程A和线程B有一种的可能执行时序为下图:
读对象的普通域被重排序到了读对象引用的前面就会出现线程B还未读到对象引用就在读取该对象的普通域变量,这显然是错误的操作。而final域的读操作就“限定”了在读final域变量前已经读到了该对象的引用,从而就可以避免这种情况。
读final域的重排序规则可以确保:在读一个对象的final域之前,一定会先读这个包含这个final域的对象的引用。
final修饰的引用类型,同样线程安全
在构造函数内对一个final修饰的对象的成员域的写入也会禁止重排序到构造函数之外。
若final引用在构造函数中溢出,则不再是线程安全。
可能的执行时序如图所示:
假设一个线程A执行writer方法另一个线程执行reader方法。因为构造函数中操作1和2之间没有数据依赖性,1和2可以重排序,先执行了2,这个时候引用对象referenceDemo是个没有完全初始化的对象,而当线程B去读取该对象时就会出错。尽管依然满足了final域写重排序规则:在引用对象对所有线程可见时,其final域已经完全初始化成功。但是,引用对象“this”逸出,该代码依然存在线程安全的问题。
因此写代码时要防止this引用溢出。
著作权归https://www.pdai.tech所有。链接:https://www.pdai.tech/md/java/thread/java-thread-x-key-final.html