final的作用从两个方面理解:基本的锁定功能,帮助JVM实现效率和安全。关于final的解释非常多,本文仅做整理的总结。
基本用法:锁定
final的锁定功能通过final修饰的对象来具体实现,final可以修饰class类/field字段/method方法,被final修饰的不同对象笼统的均处于各自不能再进行修改的状态。此类用法比较简单,知识点梳理请参考下图。
同时,不清楚的内容可以直接参考www.geeksforgeeks.org/final-keywo… 进行深入学习。
高级用法:效率与安全
1) 效率方面方面
final为JIT提供了便利,增加了JIT对代码进行内联的可行性。可参考《java编程思想》中对final提高效率的解释。
2)多线程安全方面
编译器和处理器均会对运行指令进行适当的重排序增进效率,JSR-133针对虚拟机的reordering进行了清晰地规定,其中被final修饰的字段的读load和写store指令的重排序均受到了限制:
(参考:JSR-133, www.infoq.cn/article/jav… )
同时注意,最常见的X86的处理器并不会对loadload和storestore进行重排序。本文重点讲一下final对构造器storestore的重排序的限制。
- 原理:处理器无法重排序的原因是编译器会在 final 域的写之后,插入一个 StoreStore 屏障,这个屏障禁止处理器把 final 域的写重排序到之后的store语句;
- 举例说明(参考代码块):假设有两条语句final字段的赋值(x.finalField = v; ) ,其后跟随一条赋值语句将finalField的holding对象赋值给其他线程可见的对象Y (sharedY = x;);这两条语句是不可以重排序的;
- 常常出现的场景是构造方法时new的过程被重排序,导致字段在没有赋值时被使用;此时final,volatile等阻止重排序的字段均可以保证对象安全;
class MyClass {
int i; // 非final语句
MyClass () {
i = 1;
}
}
Thread 1:MyClass clazz = new MyClass(); // clazz是共享变量
Thread 2: if (clazz != null) { System.out.println(clazz.i);}
将new分为三步:
1. 分配内存空间给var
2. 初始化var (var.i = 1)
3. class = var
在没有i final情况下,2和3的顺序可能被重排序,导致thread2在发现clazz非空时,无法获得正确的var.i
复制代码