对于final域,编译器和处理器要遵守两个重排序规则:
1: 在构造函数内对一个final域的写入,与随后把这个构造对像的引用赋值给变量,不能重排序。(禁止把final域的写重排序到构造函数外,会在final域的写之后,构造函数return之前插入一个storestore屏障)
2:
finalExample obj= new FinalExample ()。这行代码包含两个步骤:
1: 构造一个FinalExample类型的对象。
2: 把这个对象的引用赋值给引用变量obj
假设线程b读取对象引用和读对象成员域之间没有重排序,
上图中,写普通域的操作被编译器重排序到构造函数之外,读线程b错误的读取了普通变量i初始化之前的值,但final域限定在构造函数之内。final域的重排序规则可以确保在对象引用为任意线程可见之前,final域已经被初始化过了。
在一个线程中,初次读对象引用和初次读该对象包含的final域,jmm禁止处理器重排序这两个操作,编译器会在读final域操作的前面插入一个loadload屏障。初次读对象引用和初次读该对象包含的final域,这两个操作之间存在间接依赖关系。由于编译器遵守间接依赖关系,编译器不会重排序这两个操作,大多数处理器也会遵守间接依赖,大多数处理器也不会重排序这两个操作。但有的处理器会,这个规则就是专门这种处理器的。
上图中,读对象的普通域操作被处理器重排序到读对象引用之前,该域还没有被线程a写入,这是一个错误的读取操作。而读final域会将其限定在读对象的引用之后。
#### JAVA局部变量加final修饰的好处
一般来说有以下这几种用法:
1、for循环中,使用局变量来保存循环数次,并用final修饰,而非直接用getCount()、getSize()、lenght等
2、需要访问集合中的某个对象时,使用局部变量来引用,并用final修饰,而非直接引用
3、需要访问外部某个对象时,使用局部变量来引用,并用final修饰,而非直接引用
个人理解的好处有:
1、访问局部变量要比访问成员变量要快
2、访问局部变量要比每次调用方法去获取对象要快
3、使用final修饰可以避免变量被重新赋值(引用赋值)
先把成员或静态变量读到局部变量里保持一定程度的一致性,例如:在同一个方法里连续两次访问静态变量A.x可能会得到不一样的值,因为可能会有并发读写;但如果先有final int x = A.x然后连续两次访问局部变量x的话,那读到的值肯定会是一样的。这种做法的好处通常在有数据竞态但略微不同步没什么问题的场景下,例如说有损计数器之类的。
> Accessing a local variable is faster than accessing a field. It's best to keep field accesses out of performance-critical inner loops when possible. (Profile first, of course, to see if it matters.) In theory the JVM could "inline" the field to a local variable automatically under the right conditions, but don't count on it.
> A final local variable is not any different from a normal local variable at runtime. The "final" keyword on a local variable expresses a constraint on the source code (that it is assigned to once) which the compiler can easily check.
> Final *fields* do allow additional optimizations, and static final fields with primitive values (as in "public static final int MY_CONSTANT = 3") are treated as compile-time constants and inlined.
> When talking about final local variables keep in mind that using the keyword final will help the compiler optimize the code statically, which may in the end result in faster code. For example, the final Strings a + b in the example below are concatenated statically (at compile time).
```
public class FinalTest {
public static final int N_ITERATIONS = 1000000;
public static String testFinal() {
final String a = "a";
final String b = "b";
return a + b;
}
public static String testNonFinal() {
String a = "a";
String b = "b";
return a + b;
}
public static void main(String[] args) {
long tStart, tElapsed;
tStart = System.currentTimeMillis();
for (int i = 0; i < N_ITERATIONS; i++)
testFinal();
tElapsed = System.currentTimeMillis() - tStart;
System.out.println("Method with finals took " + tElapsed + " ms");
tStart = System.currentTimeMillis();
for (int i = 0; i < N_ITERATIONS; i++)
testNonFinal();
tElapsed = System.currentTimeMillis() - tStart;
System.out.println("Method without finals took " + tElapsed + " ms");
}
}
```
```
Method with finals took 5 ms
Method without finals took 273 ms
```
So how much is the actual performance improvement? I don't dare say. In most cases probably marginal (~270 nanoseconds in this synthetic test because the string concatenation is avoided altogether - a rare case), but in highly optimized utility code it might be a factor. In any case the answer to the original question is yes, it might improve performance, but marginally at best.
Compile-time benefits aside, I could not find any evidence that the use of the keyword final has any measurable effect on performance.