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

How does making a field as final on an object avoids a thread seeing a null reference on that same object?

abstract/snippet 来自 Java 并发实践-

// Unsafe publication
public Holder holder;

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

Two things can go wrong with improperly published objects. Other threads could see a stale value for the holder field, and thus see a null reference or other older value even though a value has been placed in holder. But far worse, other threads could see an up-todate value for the holder reference, but stale values for the state of the Holder. To make things even less predictable, a thread may see a stale value the first time it reads a field and then a more up-to-date value the next time, which is why assertSanity can throw 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.");
    }
}

当然,解决它的方法之一是做/做

public volatile Holder holder;

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

If Holder were immutable, assertSanity could not throw AssertionError, even if the Holder was not properly published.)

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

但是怎么办?不安全的出版物仍然存在。我认为线程仍然有可能在 holder 上获得 null 引用。求推荐

jls描述了final字段的特殊语义

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

final fields also allow programmers to implement thread-safe immutable objects without synchronization. A thread-safe immutable object is seen as immutable by all threads, even if a data race is used to pass references to the immutable object between threads. [...]. final fields must be used correctly to provide a guarantee of immutability.

但我建议你阅读整个第 17.5 章

"must be used correctly"指的是构造函数实际上已经结束(并且没有转义this)并且没有摆弄反射。

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

The end of the constructor causes a partial flush, writing all final variables and dependend objects into memory. [...]. The first reading access of a final variable causes a partial refresh, loading the final variables and depending objects from memory. Another refersh does not occur [...].