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域是一个失效值,因此将看到一个空引用或者之前的旧值”,会得到旧值的呀。为什么不会出现不正确发布呢?
换句话说,书上说不可变对象在何时都是线程安全的。那么线程在初始化这个对象时,还未初始化完全,另一个线程就获取了这个对象的引用,这个时候引用是空的。这哪里安全了?
求大佬帮忙看下。。
书上说了,问题并不处在 Holder 类,而是它的对象被发布的方式 :
在这里,在
initialize
执行的线程,holder
得到一个新对象的引用,一定在Holder
的构造结束之后。但是,由于这里没有额外的同步机制,在另一个线程里,看到
holder
里拿到一个非空引用的时候,不一定能看到Holder
构造里所有语句的结果(对 n 的赋值)。final 的问题是在 3.5.2 讲的(这点注释也说了)。
java 对 final 是有特殊处理的:
java se13 specification 17.5. final Field Semantics
标准用了 17.5 整整一节定义
final
的语义,保证final
域总是可以被“正确”的“看到”。这里的线程安全,指的只要拿到了非空引用(“发布”),那么这个引用(“发布”的“信息”)一定是“正确”的。即只要看到了发布,发布的信息就是正确的。
引用为空是还没有发布的情况,与上述线程安全是无关的。
如果你说的是拿到了一个非空的引用,但是引用的内容实际还没有完成初始化,那么见上,标准用了很大力气保证当你拿到一个非空引用的时候,其中的 final 域一定是正确初始化了的。