使用构建器/工厂模式确保内存可见性
下面的类:
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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
构建器模式通常意味着构建器是一个单独的对象。在这种情况下,您可以将正在构建的对象的字段设置为
final
,并在构建器对象调用的构造函数中初始化它们:或者,您可以确保
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: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, ifpizza
is a field of some object, you can make itvolatile
or synchronize access to it - it would ensure safe publication ofPizza
object assigned to that field.如果成员值永远不会改变,更好的模式是有一个
Pizza
构造函数,其参数为Ingredients
和Price
,并且对象上根本没有 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 parametersIngredients
andPrice
, and have no setter methods on the object at all. Really it is not useful to have asetSomething()
method that throws an exception after the first time it is called.Consider how the
String
class works. Once you have instantiated aString
with some text, the text value cannot be changed. The only way to get aString
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.