在多线程环境中初始化之前读取非最终字段的代码演示

Code demo on non-final field read before initialization in Multithread environment

Un-final field value could be incorrect

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

在阅读关于 JMM 的 JLS 第 17 章时,我和这个问题的作者有同样的困惑。我可以从字面上理解在描述的情况下 f.y 可能是 0。但这就像背诵一条规则而没有认真思考它。

如果我没有看到因不遵循 rule.I 引起的错误,我真的很难记住规则已经通过网络搜索但找不到任何人给出了以下情况的示例 f.y 可能是 0,我也想不出一个例子。

我认为这可能只发生在一些罕见的情况下,但我只是渴望看到 one.Hope 任何人都可以提供代码演示,其中 f.y 可以证明至少有一次价值0.

以下示例显示何时 y 可以是 0

class FinalFieldExample {

    final int x;
    int y;
    static FinalFieldExample f;


    FinalFieldExample() {
        x = 3;
        f = this;
        for (int i = 0; i < 1000000; i++) {
            // simulate processing
        }
        y = 4;
    }


    static void writer() {
        new FinalFieldExample();
    }


    static void reader() {
        System.out.println(f.x); // guaranteed to see 3
        System.out.println(f.y); // could see 0
    }


    public static void main(String[] args) {
        new Thread(FinalFieldExample::writer).start();
        new Thread(FinalFieldExample::reader).start();
    }
}

请注意,这不是由 JIT 编译器重新排序初始化造成的。出现此问题是因为 this 在构造期间已发布,或者更确切地说对其他线程可见。 Here 是关于相同 JLS 文档和类似示例的 post。

要创建一个示例来说明重新排序导致的这个问题,您需要了解重新排序的工作原理,但这不是确定性的事情。 有人 post 给出了一个很好的答案,解释了重新排序并链接了许多资源。

The details of what the JIT can or will do is nondeterministic. Looking at millions of samples of runs will not produce a meaningful pattern because reorderings are subjective, they depend on very specific details such as CPU arch, timings, heuristics, graph size, JVM vendor, bytecode size, etc... We only know that the JIT will assume that the code runs in a single threaded environment when it does not need to conform to the JMM. In the end, the JIT matters very little to your multithreaded code.

由于重新排序,很难在您的代码 "breaks" 中构造一个示例。 JIT 编译器显然会检查您在代码中所做的任何事情,并且不会在 "break" 逻辑的地方进行重新排序。你是对的,这种 可能会在一些罕见的情况下发生

and 有些人构建了显示重新排序的示例。你可以看到这是一个非常低级的效果。文档只说它 可以 发生但没有解释 何时 。您不应该将此视为您需要注意的 "rule"。这只是为您提供的一些额外信息。