链接构造函数时,JVM 的隐式内存屏障如何表现?

发布于 2024-08-26 03:38:25 字数 1736 浏览 12 评论 0原文

参考我的之前关于不完全构造对象的问题,我有第二个问题。正如 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 正在这样做。

Referring to my earlier question on incompletely constructed objects, I have a second question. As Jon Skeet pointed out, there's an implicit memory barrier in the end of a constructor that makes sure that final fields are visible to all threads. But what if a constructor calls another constructor; is there such a memory barrier in the end of each of them, or only in the end of the one that got called in the first place? That is, when the "wrong" solution is:

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

And the correct one would be a factory method version:

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

Would the following work too, or not?

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

Update: The essential question is that is this() guaranteed to actually call the private constructor above (in which case there would be the barrier where intended and everything would be safe), or is it possible that the private constructor gets inlined into the public one as an optimization to save one memory barrier (in which case there wouldn't be a barrier until in the end of the public constructor)?

Are the rules of this() defined precisely somewhere? If not, then I think we must assume that inlining chained constructors is allowed, and probably some JVMs or maybe even javacs are doing it.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

抠脚大汉 2024-09-02 03:38:25

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

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

I think it is safe as java memory model states that:

Let o be an object, and c be a constructor for o in which a final
field f is written. A freeze action on final field f of o takes place
when c exits, either normally or abruptly. Note that if one
constructor invokes another constructor, and the invoked constructor
sets a final field, the freeze for the final field takes place at the
end of the invoked constructor.

溺孤伤于心 2024-09-02 03:38:25

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

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

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

An object is considered to be completely initialized when its constructor finishes.

This applies also for chained constructors.

If you have to register in the constructor define the listener as a static inner class. This is safe.

感悟人生的甜 2024-09-02 03:38:25

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

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

Your second version is not correct, because it is allowing the 'this' reference to escape from the construction process. Having 'this' escape invalidates the initialization safety guarantees that give final fields their safety.

To address the implicit question, the barrier at the end of construction only happens at the very end of object construction. The intuition one reader offered about inlining is a useful one; from the perspective of the Java Memory Model, method boundaries do not exist.

我ぃ本無心為│何有愛 2024-09-02 03:38:25

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


我的猜测是它会很脆弱但还好。编译器无法知道内部构造函数是否仅从其他构造函数内部调用,因此它必须确保仅调用内部构造函数的代码结果是正确的,因此无论它使用什么机制(内存屏障?)在那里就位。

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

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

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

EDIT After the comment that suggested the compiler inlining the private constructor (I had not thought of that optimization) chances are that the code will be unsafe. And the worst part of unsafe multithreaded code is that is seems to work, so you are better off avoiding it completely. If you want to play different tricks (you do really want to avoid the factory for some reason) consider adding a wrapper to guarantee the coherence of data in the internal implementation object and register in the external object.


My guess is that it will be fragile but ok. The compiler cannot know whether the internal constructor will be called only from within other constructors or not, so it has to make sure that the result would be correct for code calling only the internal constructor, so whatever mechanism it uses (memory barrier?) has to be in place there.

I would guess that the compiler would add the memory barrier at the end of each and every constructor. The problem is still there: you are passing the this reference to other code (possibly other threads) before it is fully constructed --that is bad--, but if the only ´construction´ that is left is registering the listener, then the object state is as stable as it will ever be.

The solution is fragile in that some other day, you or some other programmer may need to add another member to the object and may forget that the chained constructors is a concurrency trick and may decide to initialize the field in the public constructor, and in doing so will add a hard to detect potential data race in your application, so I would try to avoid that construct.

BTW: The guessed safety may be wrong. I don't know how complex/smart the compiler is, and whether the memory barrier (or the like) is something it could try to optimize away... since the constructor is private the compiler does have enough information to know that it is only called from other constructors, and that is enough information to determine that the synchronization mechanism is not necessary in the internal constructor...

耳钉梦 2024-09-02 03:38:25

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

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

Escaping object reference in c-tor can publish an incompletely constructed object. This is true even if the publication is the last statement in the constructor.

Your SafeListener might not behave ok in a concurrent environment, even if c-tor inlining is performed (which I think it's not - think about creating objects using reflection by accessing private c-tor).

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文