reader() 方法包含三个操作:
  初次读引用变量 obj;
  初次读引用变量 obj 指向对象的普通域 j。
  初次读引用变量 obj 指向对象的 final 域 i。
  现在我们假设写线程 A 没有发生任何重排序,那么执行时序可能是:

  上面的图可以看到对普通变量 i 的读取重排序到了读对象引用之前,在读普通域时候,该域还没被写线程 A 写入,这是一个错误的读取操作。而读 final 域已经被 A 线程初始化了,这个读取操作是正确的。
  读 final 域的重排序规则可以确保:在读一个对象的 final 域之前,一定会先读包含 这个 final 域的对象的引用。在这个示例程序中,如果该引用不为 null,那么引用 对象的 final 域一定已经被 A 线程初始化过了。
  如果 final 域是引用类型
  如果 final 域是引用类型,写 final 域的重排序规则对编译器和处理器增加了如下约束:
  在构造函数内对一个 final 引用的对象的成员域的写入,与随后在构造函数外把这个被构造对象的引用赋值给一个引用变量,这两个操作之间不能重排序。
  如下代码例子:
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 引用对象的成员域的写入。
  避免对象引用在构造函数当中溢出
  代码如下:
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 域正确初始化之后的值。