如何在对象上将字段设为final可以避免线程在同一个对象上看到空引用?

时间:2016-08-07 10:39:32

标签: java multithreading concurrency java.util.concurrent

Java Concurrency In Practice中的摘要/片段 -

// Unsafe publication
public Holder holder;

public void initialize(){
    holder = new holder(42);
}
  

不正确发布的对象可能会出现两件事。其他   线程可以看到持有者字段的陈旧值,因此看到一个    null 引用或其他旧值,即使已有值   放在持有人。但更糟糕的是,其他线程可以看到更新   持有人参考的值,但是状态的陈旧值   支架即可。为了使事情更难以预测,一个线程可能会看到一个   第一次读取字段然后是更新时的陈旧值   下一次重视,这是为什么assertSanity可以抛出   的AssertionError

此外,对象引用对另一个线程可见,并不一定意味着该对象的状态对于使用线程是可见的

public class Holder{
    private int n;

    public Holder(int n) {
        this.n = n;
    }

    public void assertSanity(){
        if (n != n)
            throw new AssertionError("This statement is false.");
    }
}

Ofcouse,修复它的方法之一就是做/ make

public volatile Holder holder;

作者提出了一种不同的方法 -

  

如果持有人是不可变的,那么断言无法抛出   AssertionError,即使Holder未正确发布。)

public class Holder{
        private final int n;
//...
}

但是怎么样?不安全的出版物仍在那里。我认为一个线程仍然可以获得持有者的 null 引用。请提出建议。

1 个答案:

答案 0 :(得分:1)

jls描述了最终字段的特殊语义

https://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html#jls-17.5

  

final字段还允许程序员在没有同步的情况下实现线程安全的不可变对象。线程安全的不可变对象被所有线程视为不可变的,即使使用数据争用传递线程之间的不可变对象的引用也是如此。 [...]。必须正确使用最终字段以提供不可变性的保证。

但我建议你阅读整章17.5

“必须正确使用”是指构造函数实际上已经结束(并且没有转义int())并且没有摆弄反射。

翻译自http://www.angelikalanger.com/Articles/EffectiveJava/38.JMM-Overview/38.JMM-Overview.html即:

  

构造函数的结尾导致部分刷新,将所有最终变量和dependend对象写入内存。 [...]。最终变量的第一次读取访问会导致部分刷新,从内存中加载最终变量和依赖对象。另一个没有发生[...]。