由于明显的原因,下面的代码(清单16.3中的Java并发)不是线程安全的:
公共类非安全初始化{
私有静态资源;
公共静态资源getInstance(){
if(资源==null)
resource=new resource();//不安全的发布
返回资源;
}
}
然而,几页之后,在第16.3节中,他们指出:
如果
资源是不可变的,非安全初始化实际上是安全的
我不明白这种说法:
- 如果
Resource是不可变的,任何观察Resource变量的线程都会看到它为null或完全构造(这要感谢Java内存模型提供的final字段的有力保证) - 但是,没有什么可以阻止指令重新排序:特别是可以对
资源的两次读取进行重新排序(在if中有一次读取,在return中有一次读取)。因此,线程可以在if条件中看到非null的资源,但返回null引用(*)
我认为UnsafeLazyInitialization.getInstance()可以返回null,即使Resource是不可变的。是这样吗?原因是什么
(*)为了更好地理解我关于重新排序的观点,Jeremy Manson的这篇博文解释了如何通过良性数据竞争安全地发布字符串的哈希代码,以及删除局部变量如何导致哈希代码错误地返回0,由于可能的重新排序与我上面描述的非常相似:
我在这里所做的是添加一个额外的读取:在返回之前对哈希进行第二次读取。尽管听起来很奇怪,而且不太可能发生,但第一次读取可以返回正确计算的哈希值,第二次读取可以返回0!这在内存模型下是允许的,因为该模型允许对操作进行广泛的重新排序。第二次读取实际上可以在代码中移动,以便处理器在第一次读取之前执行
我想你这里的困惑是作者所说的安全出版。他指的是非空资源的安全发布,但您似乎明白了
您的问题很有趣-是否可以返回资源的空缓存值
对
编译器可以像这样对操作重新排序
公共静态资源getInstance(){
资源重新排序=资源;
if(资源!=null){
返回重新排序;
}
返回(资源=新资源());
}
这并不违反顺序一致性规则,但可以返回空值
这是否是最好的实现有待讨论,但没有规则阻止这种类型的重新排序