尽管没有代码明确泄漏它,但未初始化的对象泄漏到另一个线程?

让我们看看这个简单的 Java 程序:

Let's see this simple Java program:

import java.util.*;

class A {
    static B b;
    static class B {
        int x;
        B(int x) {
            this.x = x;
        }
    }
    public static void main(String[] args) {
        new Thread() {
            void f(B q) {
                int x = q.x;
                if (x != 1) {
                    System.out.println(x);
                    System.exit(1);
                }
            }
            @Override
            public void run() {
                while (b == null);
                while (true) f(b);
            }
        }.start();
        for (int x = 0;;x++)
            b = new B(Math.max(x%2,1));
    }
}

主线程

主线程创建 B 的实例,并将 x 设置为 1,然后将该实例写入静态字段 A.b.它会永远重复这个动作.

The main thread creates an instance of B with x set to 1, then writes that instance to the static field A.b. It repeats this action forever.

轮询线程

生成的线程会轮询,直到发现 A.b.x 不是 1.

The spawned thread polls until it finds that A.b.x is not 1.

?!?

一半的时间按预期进入无限循环,但一半的时间我得到这个输出:

Half the time it goes in an infinite loop as expected, but half the time I get this output:

$ java A
0

为什么轮询线程能够看到 x 未设置为 1 的 B?

Why is the polling thread able to see a B that has x not set to 1?

x%2 而不仅仅是 x 在这里仅仅是因为问题可以重现.

x%2 instead of just x is here simply because the issue is reproducible with it.

我在 linux x64 上运行 openjdk 6.

I'm running openjdk 6 on linux x64.

推荐答案

这是我的想法:因为 b 不是 final,编译器可以随意重新排序操作,对吧?所以这从根本上说是一个重新排序问题,结果是 不安全的发布问题将变量标记为 final 将解决问题.

Here is what I think: because b is not final, the compiler is free to reorder the operations as it likes, right? So this, fundamentally is a reordering issue and as a result a unsafe publication issue Marking the variable as final will fix the problem.

或多或少,它与 Java 内存模型文档.

真正的问题是这怎么可能.我也可以在这里推测(因为我不知道编译器将如何重新排序),但可能在写入 x 之前,对 B 的引用被写入主内存(另一个线程可见).在这两个操作之间发生读取,因此零值

The real question is how is this possible. I can also speculate here (since I have no idea how the compiler will reorder), but maybe the reference to B is written to the main memory (where it is visible to the other thread) BEFORE the write to x happens. In between these two operations the read happens, thus the zero value

相关文章