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

时间:2011-03-21 10:59:38

标签: java thread-safety volatile synchronized memory-visibility

以下课程:

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设置的成分。有一些方法可以修复它:

  • 让会员final。但是你不能使用构建器模式。
  • 同步对成员的访问权限。但这似乎是浪费,因为它们只写过一次。
  • 让他们volatile。感觉浪费,就像同步一样。
  • 使用AtomicReference
  • 等?

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

2 个答案:

答案 0 :(得分:5)

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

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

或者,您可以确保安全发布Pizza对象。请注意,安全发布惯用法应用于包含对要发布的对象的引用的字段,而不是该对象本身的字段。例如,如果pizza是某个对象的字段,则可以将其设置为volatile或同步对其的访问权限 - 这将确保安全发布分配给该字段的Pizza对象。

答案 1 :(得分:1)

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

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

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