《java并发编程实战》安全发布?

3.5 安全发布
3.5.1 不正确的发布

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.");
    }
}

由于没有使用同步来确保Holder对象对其他线程可见,因此将Holder称为“未被正确发布”。在未被正确发布的对象中存在两个问题。

① 首先,除了发布对象的线程外,其他线程可以看到的Holder域是一个失效值,因此将看到一个空引用或者之前的旧值。
② 然而,更糟糕的情况是,线程看到Holder引用的值是最新的,但Holder状态的值却是失效的。
③ 情况变得更加不可预测的是,某个线程在第一次读取域时得到失效值,而再次读取这个域时会得到一个更新值,这也是assertSainty抛出AssertionError的原因。

问题:注释中说“如果将n声明为final类型,就不会出现不正确发布的问题”。将n设为final类型,那①不是照样会发生吗?“除了发布对象的线程外,其他线程可以看到的Holder域是一个失效值,因此将看到一个空引用或者之前的旧值”,会得到旧值的呀。为什么不会出现不正确发布呢?

换句话说,书上说不可变对象在何时都是线程安全的。那么线程在初始化这个对象时,还未初始化完全,另一个线程就获取了这个对象的引用,这个时候引用是空的。这哪里安全了?

求大佬帮忙看下。。

阅读 3.7k
2 个回答

书上说了,问题并不处在 Holder 类,而是它的对象被发布的方式 :

// Unsafe publication
public Holder holder;
public void initialize() {
     holder = new Holder(42);
}

在这里,在 initialize 执行的线程,holder 得到一个新对象的引用,一定在 Holder 的构造结束之后。
但是,由于这里没有额外的同步机制,在另一个线程里,看到 holder 里拿到一个非空引用的时候,不一定能看到 Holder 构造里所有语句的结果(对 n 的赋值)。

final 的问题是在 3.5.2 讲的(这点注释也说了)。

java 对 final 是有特殊处理的:

java se13 specification 17.5. final Field Semantics

An object is considered to be completely initialized when its constructor finishes. A thread that can only see a reference to an object after that object has been completely initialized is guaranteed to see the correctly initialized values for that object's final fields.

标准用了 17.5 整整一节定义 final 的语义,保证 final 域总是可以被“正确”的“看到”。

换句话说,书上说不可变对象在何时都是线程安全的。那么线程在初始化这个对象时,还未初始化完全,另一个线程就获取了这个对象的引用,这个时候引用是空的。这哪里安全了?

这里的线程安全,指的只要拿到了非空引用(“发布”),那么这个引用(“发布”的“信息”)一定是“正确”的。即只要看到了发布,发布的信息就是正确的。

引用为空是还没有发布的情况,与上述线程安全是无关的。

如果你说的是拿到了一个非空的引用,但是引用的内容实际还没有完成初始化,那么见上,标准用了很大力气保证当你拿到一个非空引用的时候,其中的 final 域一定是正确初始化了的。

java内存模型: final field的赋值happen-before所有线程的开始

其实你继续朝后读就知道了

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题