final域
使用final域标记的变量,编译器和CPU需要遵守两个重排序规则:
构造函数内对一个final域的写入,与随后把该被构造对象的引用赋值给一个引用变量,这两个操作不能重排序。
初次读一个包含final域的对象的引用,与随后初次读这个final域, 这两个操作不能重排序。
写入示例如下:
public class FinalDemo { int i; // 普通变量 final int j; // final 变量 static FinalDemo obj; public FinalDemo { // 构造函数 i = 1; // 写普通域 j = 2; // 写 final 域 } public static void write() { // 写线程 A 执行 obj = new FinalDemo(); } public static void read() { // 读线程 B 执行 FinalDemo object = obj; // 读对象引用 int a = object.i; // 读普通域 int b = object.j; // 读 final 域 } }
下图是一种上述代码的执行时序:
在图中,写普通域的操作被编译器重排序到构造函数之外,线程B读到了i未初始化的值。而写final域的操作,被写final域的重排序规则“限定”在了构造函数之内,线程B能正确读到final变量初始后的值。
在图中,读对象的普通域被重排序到读对象引用之前。读普通域时,该域还没被写线程A写入,这是一个错误的读取操作。而final域的读操作会被“限定”在对象引用之后,此时final域已经被正确初始化。
父主题: Java同步原语