Java 使用可变变量安全发布不可变对象

Java 使用可变变量安全发布不可变对象,java,multithreading,Java,Multithreading,我看到了这样一句话: 在正确构造的对象中,所有 线程将看到正确的 最终字段,无论 对象已发布 那么为什么要使用可变变量来安全地 发布不可变对象 我真的很困惑。有人能用一个合适的例子说清楚吗?对不可变对象的易变引用可能很有用。这将允许您将一个对象交换为另一个对象,以使新数据可供其他线程使用 不过,我建议您首先考虑使用原子引用 如果您需要最终的易失性字段,那么您有一个问题。构造函数返回后,所有字段(包括最终字段)都可供其他线程使用。因此,如果将对象传递给构造函数中的另一个线程,则另一个线程可能会看到

我看到了这样一句话:

在正确构造的对象中,所有 线程将看到正确的 最终字段,无论 对象已发布

那么为什么要使用可变变量来安全地 发布不可变对象


我真的很困惑。有人能用一个合适的例子说清楚吗?

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

不过,我建议您首先考虑使用原子引用


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

在这种情况下,波动只会确保新对象的可见性;任何其他通过非易失性字段获得对象的线程都会看到最终字段的正确值

尽管如此,让变量波动并没有什么坏处;无论如何,从内存管理的角度来看是正确的;并且对于在构造函数中初始化的非final字段是必需的(尽管在不可变对象中不应该有这些字段)。如果希望在线程之间共享变量,则需要确保足够的同步以提供可见性;虽然在本例中您是对的,但构造函数的原子性没有危险

感谢Tom Hawtin指出我完全忽略了JMM对最终字段的保证;下面给出了以前的错误答案


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

  • 后续从不同线程读取该变量时,保证可以看到新值。如果不将变量标记为volatile,这些线程可能会看到引用的过时值
  • 这种关系发生在对编译器可以执行的重新排序设置限制之前。如果没有volatile变量,对变量的赋值可能发生在对象的构造函数运行之前——因此其他线程可以在对象完全构造之前获得对该对象的引用
  • 由于不可变对象的基本规则之一是在构造函数期间不发布引用,因此这里可能引用的是第二点。在没有适当并发处理的多线程环境中,对对象的引用有可能在构建该对象之前“发布”。因此,另一个线程可以获取该对象,查看其字段之一为
    null
    ,然后查看该“不可变”对象已更改


    请注意,如果您有其他适当的同步原语(例如,如果赋值(以及所有后续读取)是在给定监视器上的
    synchronized
    块中完成的),则不必使用易失性字段来实现这一点,而是在“独立”意义上,将变量标记为volatile是告诉JVM“这可能由多个线程读取,请在该上下文中确保赋值安全”的最简单方法。

    您无法真正看到不可变类中的差异。请参见下面的示例。
    Myclass.class

        public static Foo getInstance(){
        if(INSTANCE == null){
            INSTANCE = new Foo();
        }
    
        return INSTANCE;
    }
    
    在上面的代码中,如果Foo声明为final
    final Foo INSTANCE;
    ),它保证在构造函数调用期间不会发布引用。部分对象构造是不可能的


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

    JVM spec final字段语义意味着不安全发布(适当设计)不可变对象是安全的。您可能有更多的理由将引用指定给易失性字段,但如果没有它,您将看不到未初始化的最终字段。@Tom-这一点很好,这完全使我的答案无效。我将进行适当更新。此线程如何安全?因为final字段是可变的。其他线程可能会正确地看到不同的值