Java惰性线程安全单例与最终字段一起实现

时间:2017-10-21 20:44:59

标签: java concurrency singleton final

我不明白为什么这里需要局部变量:

public class FinalWrapper<T> {
    public final T value;
    public FinalWrapper(T value) {
        this.value = value;
    }
}

public class Foo {
   private FinalWrapper<Helper> helperWrapper;

   public Helper getHelper() {
      FinalWrapper<Helper> tempWrapper = helperWrapper;

      if (tempWrapper == null) {
          synchronized(this) {
              if (helperWrapper == null) {
                  helperWrapper = new FinalWrapper<Helper>(new Helper());
              }
              tempWrapper = helperWrapper;
          }
      }
      return tempWrapper.value;
   }
}

我从https://en.wikipedia.org/wiki/Double-checked_locking#Usage_in_Java获得此代码。如果我们没有这个局部变量,我们可以有什么问题?根据维基文章:

  

Java 5中最终字段的语义可用于安全地发布帮助程序对象而不使用volatile。正确性需要局部变量tempWrapper:只需使用helperWrapper进行空检查,并且由于Java内存模型允许的读取重新排序,return语句可能会失败。此实现的性能不一定比volatile实现更好。

提前致谢。

1 个答案:

答案 0 :(得分:1)

要理解根本问题,让我们从代码中删除局部变量:

public class Foo {
    private FinalWrapper<Helper> helperWrapper;

    public Helper getHelper() {
        if (helperWrapper == null) {
            synchronized(this) {
                if (helperWrapper == null) {
                    helperWrapper = new FinalWrapper<Helper>(new Helper());
                }
            }
        }
        return helperWrapper.value;
    }
}

在这种情况下我们有三个读取:

  1. 外部空检查。
  2. 内部空检查。
  3. 返回前的阅读。
  4. 问题在于,由于读取重新排序,第一次读取可以返回非null 值,第三次读取可以返回 null 。这意味着第三次读取发生在第一次读取之前,应该确保helperWrapper被初始化...

    添加局部变量可以解决问题,因为我们将helperWrapper值分配给tempWrapper,然后按{C}}读取的顺序无关紧要。如果它具有非null值,则它既用于null检查又用于return语句。

    可能会发生这种情况,因为Java内存模型允许仅为优化目的重新排序操作。查看here的引用:

      

    重新排序是什么意思?

         

    在许多情况下访问程序变量   (对象实例字段,类静态字段和数组元素)可以   似乎以不同于指定的顺序执行   程序。编译器可以自由地使用自由命令   优化名称中的说明。处理器可以执行   在某些情况下,指令无序。数据可能是   在寄存器,处理器缓存和主存储器之间移动   不同于程序指定的顺序。

         

    [...]

         

    编译器,运行时和硬件应该密谋创建   as-if-serial语义的错觉,意味着在a   单线程程序,程序应该无法观察到   重新排序的影响。但是,重新排序可以发挥作用   错误同步的多线程程序,其中一个线程是   能够观察其他线程的影响,并且可能   检测变量访问对于其他线程是否可见   不同于程序中执行或指定的顺序。

         

    [...]