在链接构造函数时,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()保证实际调用上面的私有构造函数(在这种情况下会有预期的屏障,一切都是安全的),或者私有构造函数可能被内联到公众中一个作为优化来保存一个内存屏障(在这种情况下,直到公共构造函数结束时才会出现障碍)?

this()的规则是否恰好在某个地方定义了? 如果没有,那么我认为我们必须假设内联链接构造函数是允许的,并且可能是一些JVM或甚至javac正在这样做。

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

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

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

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

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

你的第二个版本不正确,因为它允许’this’引用从构造过程中逃脱。 使用’this’转义会使初始化安全保证无效,从而使最终字段具有安全性。

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

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


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

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

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

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

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

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