Java惰性线程安全单例,并通过final字段实现 [英] Java lazy thread safe singleton with implemented with final field

查看:104
本文介绍了Java惰性线程安全单例,并通过final字段实现的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I do not understand why local variable is needed here:

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 。如果没有此局部变量,我们会遇到什么问题?根据Wiki文章:

I get this code from: https://en.wikipedia.org/wiki/Double-checked_locking#Usage_in_Java. What issues can we have if we do not have this local variable? According to the wiki article:


Java 5中final字段的语义可用于安全地发布辅助对象,而无需使用volatile。局部变量tempWrapper是正确性所必需的:仅对空检查使用helperWrapper,并且由于Java内存模型允许的读取重新排序,return语句可能会失败。此实现的性能不一定比不稳定的实现好。

Semantics of final field in Java 5 can be employed to safely publish the helper object without using volatile. The local variable tempWrapper is required for correctness: simply using helperWrapper for both null checks and the return statement could fail due to read reordering allowed under the Java Memory Model. Performance of this implementation is not necessarily better than the volatile implementation.

预先感谢。

推荐答案

要了解基本问题,我们从代码中删除局部变量:

To understand the underlying issue let's remove the local variable from the code:

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;
    }
}

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

We have three reads in this case:


  1. 外部空检查。

  2. 内部空检查。

  3. 返回前的读操作。

问题在于,由于对读操作进行重新排序,因此第一个读操作可能返回 null 值,并且三次读取可以返回 null 。这意味着第三次读取是在发生在之前,应该是要确保 helperWrapper 被初始化...

The problem is that due to the read reordering the first read can return a non-null value and the third read can return null. It means that the third read happens before the first one, which is supposed to ensure helperWrapper is initialized...

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

Adding the local variable solves the issue because we assign helperWrapper value to tempWrapper and then it does not matter in what order tempWrapper is read. If it has a non-null value it is used both for the null check and for the return statement.

之所以会发生,是因为Java内存模型允许对操作进行这种重新排序仅出于优化目的。在此处

It can happen because Java Memory Model allows for such reordering of operations just for the optimization purpose. Look at the quote from here:


重新排序是什么意思?

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

There are a number of cases in which accesses to program variables (object instance fields, class static fields, and array elements) may appear to execute in a different order than was specified by the program. The compiler is free to take liberties with the ordering of instructions in the name of optimization. Processors may execute instructions out of order under certain circumstances. Data may be moved between registers, processor caches, and main memory in different order than specified by the program.

[...]

应该假定编译器,运行时和硬件共同构成
的as-if-serial语义幻觉,这意味着在
单线程程序,该程序应该不能观察重新排序的
效果。但是,重新排序可以在
不正确同步的多线程程序中起作用,其中一个线程是
能够观察其他线程的影响,并且可能能够
检测到变量访问对
顺序与程序中执行或指定的顺序不同的其他线程。

The compiler, runtime, and hardware are supposed to conspire to create the illusion of as-if-serial semantics, which means that in a single-threaded program, the program should not be able to observe the effects of reorderings. However, reorderings can come into play in incorrectly synchronized multithreaded programs, where one thread is able to observe the effects of other threads, and may be able to detect that variable accesses become visible to other threads in a different order than executed or specified in the program.

[...]

这篇关于Java惰性线程安全单例,并通过final字段实现的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆