Java中volatile字段是否能获得与final字段相同的初始化可见性保障?
问题解答
核心结论
final字段的构造初始化可见性保障不适用于volatile字段- 即使将示例中的
y修改为volatile,依然有可能观测到j=0的结果
原因解释
JMM针对final字段做了专门的特殊规定:
当对象的构造函数执行完毕时,该对象被视为完全初始化。仅在对象完全初始化后才能看到该对象引用的线程,保证能看到该对象final字段的正确初始化值。
这个规则是final字段独有的,volatile字段没有对应的设计,它的可见性保障只针对自身读写操作的顺序,不涉及构造函数执行和对象引用发布的重排序限制。
你给出的示例场景如下:
class FinalFieldExample { final int x; int y; static FinalFieldExample f; public FinalFieldExample() { x = 3; y = 4; } static void writer() { f = new FinalFieldExample(); } static void reader() { if (f != null) { int i = f.x; // 保证一定能读到3 int j = f.y; // 可能读到0 } } }
这里的核心问题是静态变量f是普通字段,没有同步或volatile修饰,属于不安全的对象发布:JMM允许将f = new FinalFieldExample()的执行流程重排序为:
- 分配
FinalFieldExample的内存空间,所有字段默认初始化为0(此时x=0,y=0) - 将内存地址赋值给静态变量
f,此时f已经不为null - 执行构造函数内的字段赋值操作(x=3、y=4)
哪怕y被修改为volatile,也没有规则能阻止步骤2和步骤3的重排序,所以reader线程依然可能在步骤2执行完、步骤3还没执行的时候读取到f!=null,进而拿到y的默认初始值0。
如果要避免这个问题,你可以将静态变量f修饰为volatile,此时对f的volatile写会禁止构造操作重排到赋值给f之后,就能保证reader线程读到非null的f时,构造函数已经执行完成。
内容的提问来源于stack exchange,提问作者Hlib
相关产品推荐
相关产品推荐

