下面的代码(Java Concurrency in Practice清单16.3)由于显而易见的原因而不是线程安全的:
public class UnsafeLazyInitialization {
private static Resource resource;
public static Resource getInstance() {
if (resource == null)
resource = new Resource(); // unsafe publication
return resource;
}
}
Run Code Online (Sandbox Code Playgroud)
但是,几页之后,在第16.3节中,他们指出:
UnsafeLazyInitialization如果Resource是不可变的,实际上是安全的.
我不明白这句话:
Resource是不可变的,那么观察resource变量的任何线程都将看到它为null或完全构造(由于Java内存模型提供的最终字段的强保证)resource可以重新排序(在一个读取if和一个读取return).因此,线程可以resource在if条件中看到非null 但返回空引用(*).我认为UnsafeLazyInitialization.getInstance()即使Resource是不可变的也可以返回null .是这样的,为什么(或为什么不)?
(*)为了更好地理解我关于重新排序的观点,Jeremy Manson的博客文章是JLS并发的第17章的作者之一,解释了如何通过良性数据竞争安全地发布String的哈希码以及如何删除使用局部变量可能导致哈希码错误地返回0,因为可能的重新排序非常类似于我上面描述的:
我在这里做的是添加一个额外的读取:在返回之前的第二次读取哈希.听起来很奇怪,并且不太可能发生,第一次读取可以返回正确计算的散列值,第二次读取可以返回0!这在内存模型下是允许的,因为该模型允许对操作进行大量重新排序.第二次读取实际上可以在您的代码中移动,以便您的处理器在第一次读取之前完成!