不变性和重新排序

ass*_*ias 42 java concurrency multithreading java-memory-model

下面的代码(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).因此,线程可以resourceif条件中看到非null 但返回空引用(*).

我认为UnsafeLazyInitialization.getInstance()即使Resource是不可变的也可以返回null .是这样的,为什么(或为什么不)?


(*)为了更好地理解我关于重新排序的观点,Jeremy Manson的博客文章是JLS并发的第17章的作者之一,解释了如何通过良性数据竞争安全地发布String的哈希码以及如何删除使用局部变量可能导致哈希码错误地返回0,因为可能的重新排序非常类似于我上面描述的:

我在这里做的是添加一个额外的读取:在返回之前的第二次读取哈希.听起来很奇怪,并且不太可能发生,第一次读取可以返回正确计算的散列值,第二次读取可以返回0!这在内存模型下是允许的,因为该模型允许对操作进行大量重新排序.第二次读取实际上可以在您的代码中移动,以便您的处理器在第一次读取之前完成!

Joh*_*int 3

我认为您在这里感到困惑的是作者所说的安全出版的含义。他指的是非空资源的安全发布,但您似乎明白了。

您的问题很有趣 - 是否可以返回资源的空缓存值?

是的。

允许编译器像这样重新排序操作

public static Resource getInstance(){
   Resource reordered = resource;
   if(resource != null){
       return reordered;
   }
   return (resource = new Resource());
} 
Run Code Online (Sandbox Code Playgroud)

这并不违反顺序一致性规则,但可能返回空值。

这是否是最好的实现还有待争论,但没有规则来阻止这种类型的重新排序。

  • @SeanOwen,*没有正式的*指南如何将java源代码编译为字节码。面对缺乏易失性的情况,java源编译器和JVM都可以根据需要进行任何重新排序,并且只要顺序执行符合规范,它就是合法的。从技术上讲,你可以在任何地方放置一个繁忙的循环,这是合法的。您将实施质量误认为是 JLS。 (2认同)
  • @proskor,*但这不仅仅是重新排序,这些优化可以完全模糊程序* - 模糊并不意味着错误,JIT 会进行大量优化并重新排序(如果认为合适),CPU 通常使用乱序执行和分支预测。Alpha CPU 实际上可以猜测一个值,然后检查猜测是否符合预期……这一切都是公平的游戏。这就是 JMM 存在的原因——在必要时禁止此类执行。 (2认同)

归档时间:

查看次数:

2545 次

最近记录:

6 年,7 月 前