相关疑难解决方法(0)

不变性和重新排序

下面的代码(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!这在内存模型下是允许的,因为该模型允许对操作进行大量重新排序.第二次读取实际上可以在您的代码中移动,以便您的处理器在第一次读取之前完成!

java concurrency multithreading java-memory-model

42
推荐指数
1
解决办法
2545
查看次数