You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
android-notes/blogs/final.md

106 lines
4.9 KiB

6 years ago
---
final
---
#### 目录
1. final
- 引言
- 内存语义
- 优势所在
- 天生线程安全
- 编译器优化
- 参考
#### 引言
说起 final 关键字,脱口而出就是修饰变量变量不可变、修饰方法方法不可重写、修饰类类不可继承。
或许你还听过 final 能够提高效率,那如何提高效率的呢?方法内联?方法内联又是个什么东西呢?
带着疑问来探索一下 final。
#### 内存语义
##### 写 final 域的重排序规则
写 final 域的重排序规则禁止把 final 域的写重排序到构造函数之外,这个规则的实现包含两个方面:
1. JVM 禁止编译器把 final 域的写重排序到构造函数之外
2. 编译器会在 final 域的写之后,构造函数 return 之前,插入一个 StoreStore 内存屏障,这个屏障禁止处理器把 final 域的写重排序到构造函数之外
写 final 域的重排序规则可以确保:在对象引用为任何线程可见之前,对象的 final 域已经被正确初始化过了,而普通域不具有这个保证。
##### 读 final 域的重排序规则
在一个线程中,初次读对象引用与初次读改对象包含的 final 域,JMM 禁止处理器重排序这两个操作。编译器会在读 final 域操作的前面插入一个 LoadLoad 内存屏障。
读 final 域的重排序可以确保:在读一个对象的 final 域之前,一定会先读包含这个 final 域的对象引用。
#### 优势
##### 线程安全
这个不用多说,不可变对象天生线程安全。
##### 编译器优化
经常能听到别人说 final 能提高效率,在于编译器在对 final 修饰的方法做了方法内联。这句话对与否先不讨论,先来了解一下方法内联是什么鬼?
在了解方法内联之前,先来了解一下函数调用过程:
1. 首先会有个执行栈,存储它们的局部变量、方法名、动态链接地址
2. 当一个方法调用,一个新的栈桢会被加到栈顶,分配的本地变量和参数会存储在这个栈桢
3. 调转到目标方法代码执行
4. 方法返回的时候,本地方法和参数被销毁,栈顶被移除
5. 返回原来的地址执行
其实这就是虚拟机栈执行方法的内存模型。函数调用需要一定的时间开销和空间开销,当一个方法被频繁调用时,这个时间和空间开销会相对变的很大。根据二八原则,80% 的性能消耗其实是发生在 20% 的代码上,对热点代码的针对优化可以提升整体系统的性能。
然后我们在看看方法内联到底是什么?其实可以用以下例子粗鄙的解释:
```java
private int add2(int x1 , int x2 , int x3 , int x4) {
return add1(x1 , x2) + add1(x3,x4);
}
private int add1(int x1 , int x2) {
return x1 + x2;
}
```
在运行一段时间后,代码可能就变成下面这样了:
```java
private int add2(int x1 , int x2 , int x3 , int x4) {
//return add1(x1 , x2) + add1(x3,x4);
return x1 + x2 + x3 + x4;
}
```
嘿,看来方法内联就是把调用的方法函数代码复制到了调用函数内,减少因函数调用而带来的开销。
但是方法内联也是有条件的:
1. 热点代码
2. 方法体不能太大
方法内联会导致拷贝代码副本过多,代码占用内存增加,所以方法体不能太大。
其次由于方法可能被继承,导致需要类型检查而没有达到性能的效果,所以想要对热点代码进行方法内联,最好尽量使用 final、private、static 这些修饰方法,避免因为继承而导致额外的类型检查。
说到这,其实就能理解 final 能够提高效率的原因在于哪了,**但是如果想通过 final 来提升性能,那几乎是不太可能。**
现在,让我来回答一下 final 能够提高性能这个说法?
emmmm,这样的说法太片面。final 的确能够提升性能,但毕竟微乎其微。final 修饰的变量值多数情况下就能在编译阶段确定下来,这就可能导致某些代码被编译器优化掉。其次,关于方法内联的说法,也的确存在。但是方法内联也是需要条件的,首先是被频繁调用的热点代码,方法内联能够减少方法调用所带来的性能消耗;其次是方法体不能过大,原因很简单,方法内联将会导致拷贝代码更多,代码占用更多的内存空间。最后才是方法最好能被 final、private、static 修饰,这样就能避免过多的参数检查、类型检查等等。我们更应该关注 final 所带来的特性,比如属性不可变、方法不可重写、类不可继承,想要通过 final 来提升性能,几乎是不可能的事。
#### 参考
[深入理解Java中的final关键字](http://www.importnew.com/7553.html)
[Final of Java,这一篇差不多了](https://www.jianshu.com/p/f68d6ef2dcf0)
[final修饰递归方法会提高效率吗?](https://www.zhihu.com/question/66083114)
[Java 方法内联](https://www.cnblogs.com/xyz-star/p/10152564.html)