Skip to content

Commit dafec76

Browse files
committed
修订格式
1 parent 6547570 commit dafec76

File tree

1 file changed

+6
-0
lines changed

1 file changed

+6
-0
lines changed

6.你以为你真的了解final吗?/java关键字--final.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ final能够修饰变量,方法和类,也就是final使用范围基本涵盖
99

1010

1111
![final修饰成员变量](http://upload-images.jianshu.io/upload_images/2615789-768017317b5fab78.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
12+
1213
看上面的图片已经将每种情况整理出来了,这里用截图的方式也是觉得在IDE出现红色出错的标记更能清晰的说明情况。现在我们来将这几种情况归纳整理一下:
1314

1415
1. **类变量**:必须要在**静态初始化块**中指定初始值或者**声明该类变量时**指定初始值,而且只能在这**两个地方**之一进行指定;
@@ -17,6 +18,7 @@ final能够修饰变量,方法和类,也就是final使用范围基本涵盖
1718
final局部变量由程序员进行显式初始化,如果final局部变量已经进行了初始化则后面就不能再次进行更改,如果final变量未进行初始化,可以进行赋值,**当且仅有一次**赋值,一旦赋值之后再次赋值就会出错。下面用具体的代码演示final局部变量的情况:
1819

1920
![final修饰局部变量](http://upload-images.jianshu.io/upload_images/2615789-7077bdb169d4d1c3.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
21+
2022
现在我们来换一个角度进行考虑,final修饰的是基本数据类型和引用类型有区别吗?
2123

2224
> **final基本数据类型 VS final引用数据类型**
@@ -104,6 +106,7 @@ final局部变量由程序员进行显式初始化,如果final局部变量已
104106
父类会被final修饰,当子类继承该父类的时候,就会报错,如下图:
105107

106108
![final类不能继承](http://upload-images.jianshu.io/upload_images/2615789-835b66d960e21e2e.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
109+
107110
# 3. final的例子 #
108111
final经常会被用作不变类上,利用final的不可更改性。我们先来看看什么是不变类。
109112
> 不变类
@@ -168,6 +171,7 @@ JDK中提供的八个包装类和String类都是不可变类,我们来看看St
168171
我们来画下存在的一种可能执行时序图,如下:
169172

170173
![final域写可能的存在的执行时序](http://upload-images.jianshu.io/upload_images/2615789-9e3937df955a9862.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/800)
174+
171175
由于a,b之间没有数据依赖性,普通域(普通变量)a可能会被重排序到构造函数之外,线程B就有可能读到的是普通变量a初始化之前的值(零值),这样就可能出现错误。而final域变量b,根据重排序规则,会禁止final修饰的变量b重排序到构造函数之外,从而b能够正确赋值,线程B就能够读到final变量初始化后的值。
172176

173177
因此,写final域的重排序规则可以确保:**在对象引用为任意线程可见之前,对象的final域已经被正确初始化过了,而普通域就不具有这个保障**。比如在上例,线程B有可能就是一个未正确初始化的对象finalDemo。
@@ -187,6 +191,7 @@ read()方法主要包含了三个操作:
187191

188192

189193
![final域读可能存在的执行时序](http://upload-images.jianshu.io/upload_images/2615789-2a93b67948d7fc64.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/800)
194+
190195
读对象的普通域被重排序到了读对象引用的前面就会出现线程B还未读到对象引用就在读取该对象的普通域变量,这显然是错误的操作。而final域的读操作就“限定”了在读final域变量前已经读到了该对象的引用,从而就可以避免这种情况。
191196

192197
读final域的重排序规则可以确保:**在读一个对象的final域之前,一定会先读这个包含这个final域的对象的引用。**
@@ -281,6 +286,7 @@ JMM可以确保线程C至少能看到写线程A对final引用的对象的成员
281286
可能的执行时序如图所示:
282287

283288
![final域引用可能的执行时序](http://upload-images.jianshu.io/upload_images/2615789-e020492056ee1242.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
289+
284290
假设一个线程A执行writer方法另一个线程执行reader方法。因为构造函数中操作1和2之间没有数据依赖性,1和2可以重排序,先执行了2,这个时候引用对象referenceDemo是个没有完全初始化的对象,而当线程B去读取该对象时就会出错。尽管依然满足了final域写重排序规则:在引用对象对所有线程可见时,其final域已经完全初始化成功。但是,引用对象“this”逸出,该代码依然存在线程安全的问题。
285291

286292

0 commit comments

Comments
 (0)