在 Java 中,final
关键字可以用于修饰类字段(属性)。对于基本类型的 final
字段,重排序行为遵循一些特定的规则。在讨论这些规则之前,首先理解以下几个概念是必要的。
1. final
字段初始化
-
初始化一次:
final
字段在构造器中一经赋值后就不能再被修改。对于基本数据类型,final
字段的值一旦被设定,就保持不变。 -
确保可见性:如果一个线程看到一个对象的
final
字段的值,那么它也可以保证在此之前对该对象的其他final
字段的值也能被正确地看到。
2. 重排序与编译器优化
- 编译器和运行时环境(JVM)可能会对代码进行优化,包括指令重排序。这可能会影响代码的执行顺序,但
final
字段的特殊性使得它们受到一些特殊规则的保护。
3. final
字段的重排序规则
对于基本类型的 final
字段,重排序规则遵循以下原则(遵循 Java 内存模型的规范):
-
写入
final
字段后的可见性:- 如果一个线程在构建对象时写入了
final
字段,其他线程在获取这个对象的引用后,能够看到已经完成的构造。这意味着,其他线程在读取final
字段时,能够获得最新的值。
- 如果一个线程在构建对象时写入了
-
写操作顺序:
- 在构造函数中对
final
字段的写操作,完成后会确保其他线程必须在此之后才能看到这些更新。这保证了对final
字段写入的顺序性。
- 在构造函数中对
-
禁止重排序:
- 对
final
字段的写入操作禁止重排序,以确保对该字段的写入在内存上是完整的。当你在构造函数中写入final
字段时,JVM 会插入内存屏障(Memory Barriers),以防止指令重排序。
- 对
示例代码
class Example {
private final int x;
public Example() {
x = 10; // 写入final字段
}
public int getX() {
return x; // 确保可见性与顺序
}
}
public class Test {
public static void main(String[] args) {
Example example = new Example();
System.out.println(example.getX()); // 输出10
}
}
在以上示例中,x
是一个 final
字段。在 Example
构造方法中赋值后,任何其他线程在获取 example
这个对象的引用后,都能正确看到 x
的值为 10
。
总结
-
final
字段保持一致性:对于基本类型的final
字段,赋值后不会被修改,这一点在并发环境中非常重要。 -
防止重排序:对
final
字段的写操作禁止重排序,这保证了在构造期间写入的final
字段的值在其他线程中具有可见性。 -
其他字段的可见性:如果一个对象的
final
字段已经被其他线程可见,那么它在构造函数中赋值的其他final
字段的值同样是可见的。
如果你还有其他问题或者需要进一步探讨,请随时提问!