最终字段语义

huangapple go评论83阅读模式
英文:

Final Field Semantics

问题

17.5. final Field Semantics

示例 17.5-1. Java 内存模型中的 final 字段

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.y 可能会看到 0?

英文:

17.5. final Field Semantics

Example 17.5-1. final Fields In The Java Memory Model

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
        } 
    } 
}

I have been troubled by this problem for several days.

Could anyone directly answer me why it the f.y could be see 0?

答案1

得分: 2

如果线程A赋值y

线程A:
writer();

而线程B读取y

线程B:
reader();

那么y在没有任何同步的情况下被读取,因此可能无法看到赋值的值。这是Java同步要求的一个简单应用。如果您仍然不理解,请在您的问题中进行澄清。

换句话说,如果将y声明为volatile,那么将保证能够看到它。

class FinalFieldExample {
    final int x;
    volatile int y;

然后:

static void reader() {
    if (f != null) {
        int i = f.x;  // 保证能看到3
        int j = f.y;  // 保证能看到4
英文:

If thread A assigns y:

Thread A:
   writer();

And thread B reads y:

Thread B
   reader();

Then y has been read without any synchronization and therefore might not see the value assigned. It's a simple application of Java's synchronization requirement. If you still don't understand it please clarify in your question.

To put this another way, if y were declared as volatile then it would be guaranteed to be seen.

class FinalFieldExample { 
    final int x;
    volatile int y; 

Then:

static void reader() {
    if (f != null) {
        int i = f.x;  // guaranteed to see 3  
        int j = f.y;  // guaranteed to see 4

答案2

得分: 1

这是因为在写操作[this.]y=4和读操作f.y之间缺乏happens-before关系。赋值y=4在构造函数完成之前发生,构造函数在对静态字段赋值之前完成,但由于两个线程直接访问静态字段,且它们之间没有直接的顺序关系,因此没有_正式保证_非final字段y的写入在读取线程中是可见的。Java语言规范(JLS)确实对final字段做出了_特定_的承诺;实质上,存在一条未明确表达的_happens-before_规则(可能本应如此)。

任何建立_happens-before_关系的机制都将解决这个理论问题,包括将yf之中的任意一个声明为volatile

(需要注意的是,我非常怀疑曾经存在过任何一种实现,会重新排序使得对f的赋值在构造函数完成之前执行,但JLS在指出除了对final字段外,并没有_保证_不会发生这种情况。)

英文:

This is because there is no happens-before relationship between the write operation [this.]y=4 and the read operation f.y. The assignment y=4 happens-before the constructor finishes, and the constructor finishes before the assignment to the static field, but since both threads are directly accessing a static field and do not have a direct joint sequencing relationship, there is no formal guarantee that the write to the non-final field y is visible in the reading thread. The JLS does make a specific promise about final fields; in essence, there is a happens-before rule that isn't being expressed explicitly (and probably should).

Any mechanism for creating a happens-before relationship would solve this theoretical problem, including making either y or f volatile.

(Note that I very seriously doubt that any implementation has ever existed that would resequence such that the assignment to f was executed before the constructor finished, but the JLS is making the point that there is no guarantee except in the case of final fields that this won't happen.)

huangapple
  • 本文由 发表于 2020年10月26日 11:41:56
  • 转载请务必保留本文链接:https://go.coder-hub.com/64531248.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定