【问题标题】:final vs volatile guaranntee w.rt to safe publication of objectsfinal vs volatile 保证对象的安全发布
【发布时间】:2013-02-09 17:03:24
【问题描述】:

摘自《Java concurrency in practice》一书:

为了安全地发布对象, 引用对象和 对象的状态必须对 其他线程同时进行。一种 正确构造的对象可以是 安全发布者:

  • 从静态初始化器初始化对象引用

  • 将对其的引用存储到 volatile 字段或 AtomicReference 中

  • 将对它的引用存储到正确构造的最终字段中 对象

  • 将对它的引用存储到由 a
    正确保护的字段中 锁。

我的问题是:

  1. 要点 2 和 3 之间有什么区别?我对volatile 方法和final 方法在对象的安全发布方面的区别感兴趣。
  2. 他在第 3 点中所说的 正确构造对象的最终字段 是什么意思?在开始项目符号点之前,作者已经提到他们正在谈论一个正确构造的对象(我认为这不会让 this 引用 escape )。但是为什么他们又一次提到了正确构造的对象?

【问题讨论】:

    标签: java java-memory-model


    【解决方案1】:

    要点 2 和 3 有什么区别?

    • volatile 基本上意味着对该字段的任何写入都将从其他线程可见。因此,当您将字段声明为 volatile:private volatile SomeType field; 时,您可以保证如果构造函数写入该字段:field = new SomeType();,那么随后尝试读取 field 的其他线程将看到此分配。
    • final 具有非常相似的语义:您可以保证,如果您有一个 final 字段:private final SomeType field; 写入该字段(在声明中或在构造函数中):field = new SomeType(); 不会被重新编码,而是其他线程可见如果对象被正确发布(例如,this 没有转义)。

    很明显,主要的不同是如果字段是final的,你只能赋值一次。

    他在第 3 点中正确构造对象的最终字段是什么意思?

    例如,如果您让 this 从构造函数中转义,最终语义提供的保证就消失了:观察线程可能会看到具有默认值的字段(对于对象为 null)。如果对象构造正确,则不会发生这种情况。


    人为的例子:

    class SomeClass{
        private final SomeType field;
    
        SomeClass() {
            new Thread(new Runnable() {
                public void run() {
                    SomeType copy = field; //copy could be null
                    copy.doSomething(); //could throw NullPointerException
                }
            }).start();
            field = new SomeType();
        }
    }
    

    【讨论】:

    • 如果在构造之后我改变了像field.setX(new X())这样的字段引用的对象的状态,当我们将它声明为 volatile 时,是否也保证其他线程也能看到这种变化?还是它只提供初始化安全?在这种情况下 final 呢?
    • 不,只有field 为您提供(重新)分配的可见性保证。除非 x 也是 volatile,否则 field.x = new X();(或您的 setter 示例)不提供此类保证。
    • final 也是如此,推论如果 field 的所有成员也是 final 并且是不可变的,那么 field 是不可变的,因此是线程安全的。
    • @assylias 我读了你的答案几次,但仍然无法理解。既然字段是最终的,不会有任何重新排序,为什么新线程不会看到“this”的“field”属性?你是说,是因为构造函数还没有完成?
    • 在使用final 字段时,您甚至可以获得更多保证:“[另一个线程] 还将看到那些最终字段引用的任何对象或数组的版本,这些版本至少是最新的就像最后的字段一样。” (source)
    【解决方案2】:

    发布volatile和final的效果没有区别,只是final只能在构造函数中设置一次,因此你读到的内容永远不会改变。

    我相信 正确构造的对象 确实是您所指的对象,其this 引用没有逃脱其构造函数并且已以安全的方式发布到使用它的线程在。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2013-11-14
      • 1970-01-01
      • 2016-04-02
      • 2021-03-24
      • 2010-12-09
      • 1970-01-01
      • 2016-10-28
      相关资源
      最近更新 更多