标签:
对于 final 域,编译器和处理器要遵守两个重排序规则:
举个例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
public class FinalExample { int i; // 普通变量 final int j; // final 变量 static FinalExample obj; public FinalExample() { i = 1 ; // 写普通域 j = 2 ; // 写 final 域 } public static void writer() { // 写线程 A 执行 obj = new FinalExample(); } public static void reader() { // 读线程 B 执行 FinalExample object = obj; int a = object.i; int b = object.j; } } |
这里假设一个线程 A 执行 writer ()方法,随后另一个线程 B 执行 reader ()方法。
在写 final 域的时候有两个规则:
分析上面的代码。
write 方法,只包含一行 obj = new FinalExample();
,但是包含两个步骤:
假设线程 B 当中读 obj 与读成员域之间没有重排序。那么执行时序可能如下:
写 final 域的重排序规则可以确保:在对象引用为任意线程可见之前,对象的 final 域已经被正确初始化过了,而普通域不具有这个保障。
读 final 域的重排序规则如下:
reader() 方法包含三个操作:
现在我们假设写线程 A 没有发生任何重排序,那么执行时序可能是:
上面的图可以看到对普通变量 i 的读取重排序到了读对象引用之前,在读普通域时候,该域还没被写线程 A 写入,这是一个错误的读取操作。而读 final 域已经被 A 线程初始化了,这个读取操作是正确的。
读 final 域的重排序规则可以确保:在读一个对象的 final 域之前,一定会先读包含 这个 final 域的对象的引用。在这个示例程序中,如果该引用不为 null,那么引用 对象的 final 域一定已经被 A 线程初始化过了。
如果 final 域是引用类型,写 final 域的重排序规则对编译器和处理器增加了如下约束:
如下代码例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
public class FinalReferenceExample { final int [] intArray; static FinalReferenceExample obj; public FinalReferenceExample() { intArray = new int [ 1 ]; // 1 intArray[ 0 ] = 1 ; // 2 } public static void writerOne() { // A线程执行 obj = new FinalReferenceExample(); // 3 } public static void reader() { // 写线程 B 执行 if (obj != null ) { // 4 int temp1 = obj.intArray[ 0 ]; // 5 } } } |
假设首先线程 A 执行 writerOne()方法,执行完后线程 B 执行reader 方法,JMM 可以确保读线程 B 至少能看到写线程 A 在构造函数中对 final 引用对象的成员域的写入。
代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
public class FinalReferenceEscapeExample { final int i; static FinalReferenceEscapeExample obj; public FinalReferenceEscapeExample() { i = 1 ; // 1 obj = this ; // 2 避免怎么做!!! } public static void writer() { new FinalReferenceEscapeExample(); } public static void reader() { if (obj != null ) { // 3 int temp = obj.i; // 4 } } } |
假设一个线程 A 执行 writer()方法,另一个线程 B 执行 reader()方法。
这里的操作 2 使得对象还未完成构造前就为线程 B 可见。即使这里的操作 2 是构造函数的最后 一步,且即使在程序中操作 2 排在操作 1 后面,执行 read()方法的线程仍然可能无 法看到 final 域被初始化后的值,因为这里的操作 1 和操作 2 之间可能被重排序。
在构造函数返回前,被构造对象的引用不能为其他线程可 见,因为此时的 final 域可能还没有被初始化。在构造函数返回后,任意线程都将 保证能看到 final 域正确初始化之后的值。
全能程序员交流QQ群290551701,群内程序员都是来自,百度、阿里、京东、小米、去哪儿、饿了吗、蓝港等高级程序员 ,拥有丰富的经验。加入我们,直线沟通技术大牛,最佳的学习环境,了解业内的一手的资讯。如果你想结实大牛,那 就加入进来,让大牛带你超神!
标签:
原文地址:http://www.cnblogs.com/fengliucaizi/p/4929943.html