Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

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)

AltStyle によって変換されたページ (->オリジナル) /