使用Volatile变量来安全发布Immutable对象

时间:2010-12-07 11:40:02

标签: java multithreading

我发现了这句话:

  

在正确构造的物体中,全部   线程会看到正确的值   最终的领域,无论如何   对象发布。

那么为什么要使用volatile变量来安全 发布一个不可变对象?

我真的很困惑。任何人都能用合适的例子说清楚吗?

3 个答案:

答案 0 :(得分:5)

在这种情况下,波动率只会确保新对象的可见性;碰巧通过非易失性字段获取对象的任何其他线程确实会根据JSR-133's initialization safety guarantees看到最终字段的正确值。

尽管如此,使变量volatile不会受到伤害;从内存管理的角度看是正确的;对于在构造函数中初始化的非final字段,是必需的(尽管在不可变对象中不应存在任何这些字段)。如果您希望在线程之间共享变量,则需要确保足够的同步以提供可见性;虽然在这种情况下你是对的,但构造函数的 atomicity 没有危险。

感谢Tom Hawtin指出我完全忽视了JMM对最终领域的保证;之前的错误答案如下。


volatile变量的原因是在对象的构造和赋值之间建立了 happens-before 关系(根据Java内存模型)变量。这实现了两件事:

  1. 保证从不同线程读取该变量可以看到新值。如果不将变量标记为volatile,则这些线程可以看到引用的陈旧值。
  2. 发生之前关系限制了编译器可以执行的重新排序。如果没有volatile变量,变量的赋值可能在对象的构造函数运行之前发生 - 因此其他线程可以在完全构造之前获得对该对象的引用。
  3. 由于不可变对象的基本规则之一是您在构造函数期间不发布引用,因此这是第二点,可能在此引用。在没有适当并发处理的多线程环境中, 可以在构造该对象之前对要“发布”的对象进行引用。因此,另一个线程可以获取该对象,看到它的一个字段是null,然后看到这个“不可变”对象已经改变。

    请注意,如果您有其他适当的同步原语,则不必使用volatile字段来实现此目的 - 例如,如果赋值(以及所有后续读取)都在{{{ 1}}在给定的监视器上阻塞 - 但是在“独立”意义上,将变量标记为synchronized是告诉JVM的最简单方法“这可能被多个线程读取,请使分配安全上下文中,“

答案 1 :(得分:2)

对不可变对象的volatile引用可能很有用。这将允许您将一个对象替换为另一个对象,以使新数据可用于其他线程。

我建议您首先使用AtomicReference。

如果您需要最终的易失性字段,则会出现问题。一旦构造函数返回,所有字段(包括最终字段)都可用于其他线程。因此,如果将对象传递给构造函数中的另一个线程,则另一个线程可能会看到不一致的状态。恕我直言,你应该考虑一个不同的解决方案,所以你不必这样做。

答案 2 :(得分:0)

您无法真正看到Immutable类的差异。请参阅下面的示例。Myclass.class

    public static Foo getInstance(){
    if(INSTANCE == null){
        INSTANCE = new Foo();
    }

    return INSTANCE;
}

在上面的代码中,如果Foo被声明为 final final Foo INSTANCE;),它保证在构造函数调用期间它不会发布引用。部分对象构造是不可能的

考虑这个......如果这个Myclass是不可变的,它的状态在对象构造之后就不会改变,使得Volatile(volatile final Foo INSTANCE;)关键字变得冗余。但是如果这个类允许它的对象状态是更改(不可变)多个线程CAN实际更新对象,并且某些更新对其他线程不可见,因此volatile关键字确保在非Immutable类中安全发布对象