使用构建器/工厂模式确保内存可见性

发布于 2024-10-24 12:23:00 字数 1035 浏览 1 评论 0原文

下面的类:

class Pizza {

    Ingredients ingredients;
    Price price;

    public setIngredients(Ingredients ing) {
        if (ingredients != null) {
            throw new IllegalStateException();
        }
        ingredients = ing;
        return this;
    }

    public setPrice(Price p) {
        if (price != null) {
            throw new IllegalStateException();
        }
        price = p;
        return this;
    }

}

可以在构建器模式中使用,并且在构建之后,它实际上是不可变的,因为每个属性只能设置一次。也就是说:

Pizza pizza = new Pizza().setIngredients(something).setPrice(somethingelse);

但是,Pizza 不是线程安全的:不能保证线程 B 可以看到线程 A 设置到其中的成分。有一些方法可以修复它

  • : >最终。但是这样你就不能使用构建器模式了。
  • 同步对成员的访问。但这似乎是浪费,因为它们只写一次。
  • 让它们易失。感觉很浪费,就像同步一样。
  • 使用AtomicReference
  • ETC。?

我的问题是,告诉 JVM 类成员在调用某个方法后不会更改的最佳方法是什么?我是否应该只同步对它的访问,并相信 JVM 会优化锁定?这感觉很浪费,因为我知道成员应该在设置后表现得像final。难道就没有更好的解决办法吗?

The following class:

class Pizza {

    Ingredients ingredients;
    Price price;

    public setIngredients(Ingredients ing) {
        if (ingredients != null) {
            throw new IllegalStateException();
        }
        ingredients = ing;
        return this;
    }

    public setPrice(Price p) {
        if (price != null) {
            throw new IllegalStateException();
        }
        price = p;
        return this;
    }

}

could be used in a builder pattern, and after it has been built, it's effectively immutable, because each property can be set only once. That is:

Pizza pizza = new Pizza().setIngredients(something).setPrice(somethingelse);

However, Pizza is not thread safe: there are no guarantees that thread B sees the ingredients that were set into it by the thread A. There are some ways to fix it:

  • Make members final. But then you can't use a builder pattern.
  • Synchronize access to members. But this seems like waste, because they're written only once ever.
  • Make them volatile. Feels waste, like synchronization.
  • Use AtomicReference.
  • Etc.?

My question is, what is the best way to tell the JVM that a class member won't change after some method has been called? Should I just synchronize access to it, and trust that the JVM will optimize the lock away? It just feels waste, because I know that the member should behave like it's final after it's set. Aren't there any better solutions?

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

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

发布评论

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

评论(2

花落人断肠 2024-10-31 12:23:00

构建器模式通常意味着构建器是一个单独的对象。在这种情况下,您可以将正在构建的对象的字段设置为 final,并在构建器对象调用的构造函数中初始化它们:

Pizza pizza = 
    new PizzaBuilder()
        .setIngredients(something)
        .setPrice(somethingelse)
        .build(); 

或者,您可以确保 Pizza 对象的安全发布。请注意,安全发布习惯用法适用于包含对正在发布的对象的引用的字段,而不是该对象本身的字段。例如,如果 pizza 是某个对象的字段,您可以使其易失性或同步对其的访问 - 这将确保 Pizza 的安全发布> 分配给该字段的对象。

Builder pattern usually means that builder is a separate object. In this case you can make fields of the object being built final, and initialize them in constructor called by the builder object:

Pizza pizza = 
    new PizzaBuilder()
        .setIngredients(something)
        .setPrice(somethingelse)
        .build(); 

Alternatively, you can ensure safe publication of the Pizza object. Note that safe publication idioms are applied to the field that contains a reference to the object being published, not to the fields of that object itself. For example, if pizza is a field of some object, you can make it volatile or synchronize access to it - it would ensure safe publication of Pizza object assigned to that field.

凉城 2024-10-31 12:23:00

如果成员值永远不会改变,更好的模式是有一个 Pizza 构造函数,其参数为 IngredientsPrice ,并且对象上根本没有 setter 方法。实际上,在第一次调用后抛出异常的 setSomething() 方法并没有什么用处。

考虑一下 String 类的工作原理。一旦用某些文本实例化了String,文本值就无法更改。获取具有不同值的 String 的唯一方法是构造一个新的。看来这就是你想要的。

使用此模式还可以避免同步问题。

If the member values are never meant to change, the a better pattern would be to have a Pizza constructor that takes as its parameters Ingredients and Price, and have no setter methods on the object at all. Really it is not useful to have a setSomething() method that throws an exception after the first time it is called.

Consider how the String class works. Once you have instantiated a String with some text, the text value cannot be changed. The only way to get a String with a different value is to construct a new one. It seems like that is what you want here.

Using this pattern also avoids the synchronization issue.

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