重排序规则
1. 在构造函数内对一个final域的写入,与随后把这个被构造对象引用赋值给一个引用变量,着两个操作不能重排序
2. 初次读一个包含final域的对象的引用,与随后初次读取这个final域,这两个操作不能重排序
以下面实例作说明:
public class FinalExample {
int i;
final int j;
static FinalExample obj;
public FinalExapmle() {
i = 1;
j = 2;
}
public static writer() {
obj = new FinalExample();
}
public static void reader() {
FinalExample object = obj;
int a = object.i;
int b = object.j;
}
}
final变量的写
JMM禁止编译器把final域的写重排序到构造函数之外,编译器会在final域的写之后,构造函数的return之前,插入一个StoreStore内存屏障。
上图显示了多线程情况下一种可能的执行顺序,final域写的重排序规则可以保证:在对象引用为任意线程可见之前,对象的final域已经被正确初始化过,而普通域不具有这个保证。
final变量的读
在一个线程中,初次读对象引用与初次读该对象的包含的final域,JMM禁止处理器重排序这两个操作。编译器会在读final域操作的前面插入一个LoadLoad屏障。
final域的重排序规则可以确保:在读一个final域变量之前,一定会先读取包含这个final域的对象的引用,上例中,如果对象的引用obj不为null则final域变量一定已经初始化完成了
Final域为引用类型
对于引用类型,在构造函数内对一个final引用的对象的成员域的写入,与随后在构造函数外把这个被针对的引用赋值给一个引用变量,这两个操作不能重排序。