JVM的隐式记忆障碍在链接构造函数时如何表现?
-
22-09-2019 - |
题
指我 关于未完全构造的对象的早期问题, ,我有第二个问题。正如乔恩·斯基特(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()
精确地定义了某个地方?如果没有 javac
S正在这样做。
解决方案
我认为这是安全的,因为Java内存模型指出:
让 o 是一个对象, C 成为 o 其中最后一个领域 F 是写的。在最终领域的冻结动作 F 的 o 发生在何时 C 正常或突然退出。请注意,如果一个构造函数调用另一个构造函数,并且调用的构造函数设置了最终字段,则最终字段的冻结发生在调用构造函数的末尾。
其他提示
当对象构造函数完成时,被认为是完全初始化的。
这也适用于链式构造函数。
如果您必须在构造函数中注册,则将侦听器定义为静态内部类。这是安全的。
您的第二版不正确,因为它允许“此”参考逃脱施工过程。让“此”逃脱无效,使初始化安全性无效,可以确保最终领域的安全性。
为了解决隐性问题,建筑结束时的障碍只发生在物体构造的末尾。直觉的一位读者提供的有关内部的信息是有用的。从Java内存模型的角度来看,方法边界不存在。
编辑 在提示编译器将私有构造函数(我没有考虑到优化)的评论之后,代码将不安全。不安全的多线程代码中最糟糕的部分似乎是有效的,因此您最好完全避免使用它。如果您想播放不同的技巧(您确实想避免出于某种原因避免工厂)考虑添加包装器以确保内部实现对象中数据的连贯性并在外部对象中注册。
我的猜测是它会很脆弱,但是还可以。编译器不知道内部构造函数是否仅在其他构造函数中调用,因此必须确保仅调用内部构造函数的代码正确的结果是正确的,因此无论它使用的任何机制(内存屏障)是否有到位。
我猜想编译器会在每个构造函数的末端添加内存屏障。问题仍然存在:您正在通过 this
引用其他代码(可能是其他线程)在完全构造之前(这是不好的),但是如果剩下的唯一的“构造”是注册侦听器,那么对象状态将保持稳定。
解决方案是 脆弱的 在其他一天中,您或其他一些程序员可能需要将另一个成员添加到对象中,并且可能会忘记链式构造函数是一个并发技巧,并且可以决定在公共构造函数中初始化该字段,并且这样做会添加一个很难在您的应用程序中检测潜在的数据竞赛,因此我会尽力避免使用该构造。
顺便说一句:猜测的安全可能是错误的。我不知道编译器有多复杂/聪明,以及是否可以尝试优化内存屏障(或类似)...由于构造函数是私有的,因此编译器确实有足够的信息来知道它是仅来自其他构造函数,这足以确定内部构造函数中不需要同步机制...
c-tor中的逃脱对象引用可以发布不完整的对象。这是真的 如果出版物是构造函数中的最后一句话.
即使执行了C-TOR内衬,您的Safelistener也可能在并发环境中行为不正常(我认为不是 - 考虑通过访问私有C-TOR来使用反射来创建对象)。