JVM 的隐式内存屏障在链接构造函数时的行为如何?

关于我之前关于不完整构造对象的问题,我还有第二个问题。正如 Jon Skeet 所指出的,构造函数的末尾有一个隐式内存屏障,可确保字段对所有线程都可见。但是,如果一个构造函数调用另一个构造函数呢?是在每个障碍的末尾都有这样的记忆屏障,还是仅在首先被调用的那个的末尾?也就是说,当“错误”的解决方案是:final

public class ThisEscape {
    public ThisEscape(EventSource source) {
        source.registerListener(
            new EventListener() {
                public void onEvent(Event e) {
                    doSomething(e);
                }
            });
    }
}

正确的一个是工厂方法版本:

public class SafeListener {
    private final EventListener listener;

    private SafeListener() {
        listener = new EventListener() {
            public void onEvent(Event e) {
                doSomething(e);
            }
        }
    }

    public static SafeListener newInstance(EventSource source) {
        SafeListener safe = new SafeListener();
        source.registerListener(safe.listener);
        return safe;
    }
}

以下方法是否也有效?

public class MyListener {
    private final EventListener listener;

    private MyListener() {
        listener = new EventListener() {
            public void onEvent(Event e) {
                doSomething(e);
            }
        }
    }

    public MyListener(EventSource source) {
        this();
        source.register(listener);
    }
}

更新:基本问题是,保证实际调用上面的私有构造函数(在这种情况下,在预期的地方会有障碍,一切都是安全的),或者私有构造函数是否有可能作为优化内联到公共构造函数中以节省一个内存屏障(在这种情况下,直到公共构造函数结束时才会有障碍)?this()

定义的规则是否精确地在某处?如果不是,那么我认为我们必须假设内联链式构造函数是允许的,并且可能一些JVM甚至可能正在这样做。this()javac


答案 1

我认为这是安全的,因为java内存模型指出:

o 是对象,co 的构造函数,其中写入了最后一个字段 f。当 c 正常或突然退出时,对 o 的最终场 f 的冻结操作发生。请注意,如果一个构造函数调用另一个构造函数,并且被调用的构造函数设置了最终字段,则最终字段的冻结发生在被调用的构造函数的末尾。


答案 2

当对象的构造函数完成时,将被视为已完全初始化。

这也适用于链接式构造函数。

如果必须在构造函数中注册,请将侦听器定义为静态内部类。这是安全的。