【问题标题】:How does making a field as final on an object avoids a thread seeing a null reference on that same object?在对象上将字段设置为最终字段如何避免线程在同一对象上看到空引用?
【发布时间】:2016-08-07 10:39:31
【问题描述】:

来自 Java Concurrency In Practice-的抽象/sn-p-

// Unsafe publication
public Holder holder;

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

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

对象引用对另一个线程可见并不一定意味着该对象的状态对消费线程可见

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;

作者提出了不同的方法-

如果 Holder 是不可变的,assertSanity 就不能抛出 AssertionError,即使 Holder 没有正确发布。)

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

但是怎么做呢?不安全的出版物仍然存在。我认为线程仍然有可能在持有人上获得 null 引用。请提出建议。

【问题讨论】:

标签: java multithreading concurrency java.util.concurrent


【解决方案1】:

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

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

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

但我建议你阅读整章 17.5

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

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

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

【讨论】:

    猜你喜欢
    • 2015-12-27
    • 2021-08-10
    • 1970-01-01
    • 2017-08-27
    • 2020-07-29
    • 1970-01-01
    • 1970-01-01
    • 2012-09-20
    • 2016-09-07
    相关资源
    最近更新 更多