当我提到JMM与“最终”相关的保证时,我最近感到困惑。以下是JMM的摘录和示例
图4给出了一个示例,演示了最终字段与普通字段的比较。班级 FinalFieldExample有一个最终的int字段x和一个非最终的int字段y。一个线程可能执行方法writer(),另一个线程可能执行方法reader()。因为writer()写f 在对象的构造函数完成后,reader()将保证看到f.x的正确初始化值:它将读取值3.但是,f.y不是最终的;因此,reader()方法不能保证看到它的值为
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; // guaranteed to see 3
int j = f.y; // could see 0
}
}
}
我的困惑是,对象'Obj'有最终和非最终字段已完全初始化并被Thread'T'引用,T只会看到最终字段的正确值?那么在构造之后没有变异的非最终字段呢?我明白,如果它们在构造之后发生变异,那么'T'可能看不到新的值(除非该字段是易失性的)。但是,如果该领域是非最终的并且是非挥发性的并且在施工后没有变异,我就是这样的人吗?
JVM如何实现与'final'相关的保证?例如,对于易失性存在记忆障碍。
答案 0 :(得分:4)
在这个答案中解决了这个问题:
引用:
问题围绕着指令的优化和重新排序。当你有两个线程使用没有同步的构造对象时,可能会发生这样的情况:编译器决定为了效率而重新排序指令并为对象分配内存空间并在它完成构造函数之前将其引用存储在item字段中字段初始化。或者它可以重新排序内存同步,以便其他线程以这种方式感知它。
如果将字段标记为final,则强制编译器在构造函数完成之前完成该字段的初始化。非最终字段没有这样的保证。
这是Java language definition (17.4)的一部分。有关final
字段的详细信息也在JLS (17.5)。
更具体地说,writer()
方法构造FinalFieldExample
的实例并存储在static
字段中以供其他线程使用。由于指令重新排序,y
字段可能尚未初始化。如果相同的帖子调用reader()
,它会将y
视为4
,但其他帖子可能会将其视为0
,因为f
在 y
初始化并发布之前,可能被设置和使用。
要使此代码正确,您必须同时f
为volatile
。
答案 1 :(得分:3)
JVM如何实现与'final'相关的保证?例如,对于易失性存在记忆障碍。
为了尊重final
字段的语义,无法进行某些重新排序,并且可能需要一些内存屏障(在某些处理器上)。见http://g.oswego.edu/dl/jmm/cookbook.html
这意味着final
不是免费午餐。在我们到处使用final
之前,请考虑到这一点。 (仅仅因为它在约书亚的书中并不意味着它是正确的)
答案 2 :(得分:1)
我的困惑是,对象'Obj'有最终和非最终字段已完全初始化并被Thread'T'引用,T只会看到最终字段的正确值?
所有线程都保证以查看final
字段的正确值。这对非最终领域一无所知。
施工后没有变异的非最终字段怎么样。
final
修饰符仅适用于特定字段。您可以使用其他非最终字段“幸运”,但保证仅将 应用于标记为final
的字段。
在构造函数中设置非final字段而不是稍后修改的事实是无关紧要的。