在 getInstance() 方法或实例变量定义中初始化单例之间是否存在功能差异

Is there a functional difference between initializing singleton in a getInstance() method, or in the instance variable definition

这两种实现单例的方式在功能上有什么区别吗?

public class MySingleton {
    private static MySingleton instance;

    public static MySingleton getInstance() {
        if (instance == null) {
            instance = new MySingleton();
        }
        return instance;
    }
}


public class MySingleton {
    private static final MySingleton instance = new MySingleton();

    public static MySingleton getInstance() {
        return instance;
    }
}

此外,第一种方法允许某种 clearInstance() 方法。尽管您可以在第二种方法中使实例不是最终的。

第一种方法在技术上是否表现更好,因为它只在第一次需要时而不是在程序启动时初始化?

Is there any functional difference between these two ways of implementing a Singleton?

是的。如果您在变量声明中使用初始化程序,则在初始化 class 时创建实例,即使从未访问该实例。如果您在 getInstance() 方法中对其进行初始化,则仅在访问该实例时才创建该实例。这具有线程安全隐患。如果初始化一个实例很便宜并且没有持久的外部副作用,那么它并没有太大的区别,但情况可能并非总是如此。

Does the first method technically perform better because it is only initialized the first time it is needed instead of when the program starts?

如果你在任何情况下都要使用一个实例,那么无论如何你都将在某个时候支付初始化它的成本,所以从这个意义上讲没有性能差异。但是,第一种方法的线程安全版本在第一次调用时会比第二种方法稍贵,并且您将在每次后续调用时再次支付额外的开销。

它是关于延迟初始化与急切初始化的。不同之处在于,在第一个实例中,在您调用 getInstance() 方法之前不会创建实例,但在第二个实例中,它甚至在您调用 getInstance() 方法之前就已经创建了。

Please refer this link if you want more info

第一个是延迟加载,第二个是预加载。也许您的应用程序从不调用单例,因此如果创建单例的新实例是消耗大量资源的操作,那么延迟加载会更好,因为它会在需要时创建新实例。

您使用的第一种方法不是线程安全的。我会认为这是一个错误。

第二种方法更简单、线程安全、速度快,而且如果您确保构造函数不会抛出愚蠢的异常,则更正。

如果您确实需要更多逻辑,可以使用第一种方法,但必须确保使用互斥体保护它。类似于:

public class MySingleton {
    private static final Object mylock = new Object();
    private static MySingleton instance;

    public static MySingleton getInstance() {
        synchronized(mylock) {
            if (instance == null) {
                instance = new MySingleton();
            }
            return instance;
        }
    }
}

显然代码更复杂,使用更多内存,速度更慢,您不能将变量声明为 final...

这两种方法都会延迟初始化单例。在 Java 中,当使用 class 时,所有变量初始化和静态构造函数都由 class 加载程序参与,而不是在代码的开头。如果您的代码路径从不调用 getInstance,则 Singleton 将永远不会被初始化。

就我个人而言,我避免使用单例,但是当我使用它们时总是在变量声明上立即分配。

更正 我 运行 进行了一些实验,结果 class 初始化与主线程的执行并行进行。它并没有像我相信的那样等待。至少在一个非常简化的测试场景中,初始化是急切的,但是是异步的。

从单元测试的角度来看,我更喜欢惰性实例化。鉴于单例的初始化有进一步的副作用(与实际测试无关),并且您想测试需要单例的 class (可能只是一个特定的方法),模拟单例并注入更容易在准备测试时将其添加到实例变量中。为您的单例实例使用模拟,您可以更轻松地控制单例方法 return 对您的 class 进行测试。

双重检查锁定模式可以最大限度地减少线程安全实例化的开销:

private static volatile MySingleton instance;

public static MySingleton getInstance() {
    if (instance == null) {
        synchronized ( MySingleton.class ) {
            if (instance == null) {
                instance = new MySingleton();
            }
        }
    }
    return instance;
}

因此只有两个(或多个)线程第一次(同时)访问单例的罕见情况可能会进入锁定状态。之后第一个 ''if null'' 将 return false 并且你再也不会进入锁定状态。

重要提示:必须将成员声明为 volatile 才能使此模式可靠地工作。

注意:上面的"double checked lock"模式已经被证明不是100%可靠的。请参阅下面评论中的讨论,尤其是 Brian Goetz' arcticle