14

参考我之前关于未完全构造的对象的问题,我还有第二个问题。正如 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()保证实际调用上面的私有构造函数(在这种情况下,预期的地方会有障碍,一切都是安全的),或者私有构造函数是否有可能作为一个内联到公共构造函数中优化以保存一个内存屏障(在这种情况下,直到公共构造函数结束时才会有屏障)?

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

4

5 回答 5

6

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

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

于 2012-04-24T16:28:03.183 回答
3

当一个对象的构造函数完成时,它被认为是完全初始化的。

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

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

于 2010-03-25T08:13:31.623 回答
3

您的第二个版本不正确,因为它允许“this”引用从构建过程中逃脱。使用“this”转义会使赋予最终字段安全的初始化安全保证无效。

为了解决隐含的问题,构造结束时的障碍只发生在对象构造的最后。一位读者提供的关于内联的直觉很有用。从 Java 内存模型的角度来看,方法边界是不存在的。

于 2010-03-25T14:16:59.110 回答
1

编辑在建议编译器内联私有构造函数的评论之后(我没有想到那个优化),代码可能是不安全的。不安全的多线程代码最糟糕的部分是它似乎可以工作,所以你最好完全避免它。如果您想玩不同的技巧(出于某种原因您确实想避免使用工厂),请考虑添加一个包装器以保证内部实现对象中数据的一致性并在外部对象中注册。


我的猜测是它会很脆弱但没关系。编译器无法知道内部构造函数是否只会在其他构造函数中被调用,所以它必须确保结果对于只调用内部构造函数的代码是正确的,所以它使用的任何机制(内存屏障?)都有在那里。

我猜编译器会在每个构造函数的末尾添加内存屏障。问题仍然存在:您在this完全构造之前将引用传递给其他代码(可能是其他线程) - 这很糟糕 - 但如果剩下的唯一“构造”是注册监听器,那么对象状态一如既往地稳定。

解决方案是脆弱的,因为有一天,您或其他一些程序员可能需要向对象添加另一个成员,并且可能会忘记链式构造函数是一种并发技巧,并且可能决定在公共构造函数中初始化字段,并且在做所以会在你的应用程序中增加一个难以检测的潜在数据竞争,所以我会尽量避免这种结构。

顺便说一句:猜测的安全性可能是错误的。我不知道编译器有多复杂/智能,以及内存屏障(或类似的东西)是否可以尝试优化......因为构造函数是私有的,编译器确实有足够的信息知道它是仅从其他构造函数调用,这足以确定内部构造函数中不需要同步机制...

于 2010-03-25T08:22:00.923 回答
1

在 c-tor 中转义对象引用可以发布不完整构造的对象。即使发布是构造函数中的最后一条语句也是如此。

您的 SafeListener 在并发环境中可能无法正常运行,即使执行了 c-tor 内联(我认为不是 - 考虑通过访问私有 c-tor 使用反射创建对象)。

于 2010-03-25T09:30:22.547 回答