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



这两种实现Singleton的方法在功能上有什么区别吗?

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((方法之外。尽管您可以在第二种方法中使实例不是最终实例。

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

第一个是懒惰加载,第二个是渴望加载。也许您的应用程序从来没有调用过singleton,所以如果创建singleton的新实例是一项耗费大量资源的操作,那么延迟加载会更好,因为它会在需要时创建新实例。

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

第二种方法更简单,线程安全,速度快,如果你确保构造函数不会抛出愚蠢的异常,那就正确了。

如果您绝对需要更多的逻辑,您可以使用第一个方法,必须确保使用互斥锁来保护它。类似于:

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。。。

这两个方法都将惰性地初始化Singleton。在Java中,当使用类时,所有变量初始化和静态构造函数都由类加载器参与,而不是在代码的开头。如果您的代码路径从未调用getInstance,则Singleton将永远不会初始化。

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

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

这两种实现Singleton的方法在功能上有什么区别吗?

是。如果在变量声明中使用初始值设定项,则在初始化类时创建实例,即使从未访问过实例。如果在getInstance()方法中对其进行初始化,则只有在访问实例时才会创建该实例。这会影响线程安全。如果初始化实例成本低廉且没有持久的外部副作用,那么在其他方面也不会有太大区别,但情况可能并非总是如此。

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

如果你在任何情况下都要使用一个实例,那么无论发生什么,你都要在某个时刻为初始化它付出代价,所以在这个意义上没有性能差异。然而,在第一次调用中,第一个方法的线程安全版本将比第二个方法稍微贵一点,并且您将在每次后续调用中再次支付额外的开销。

关于Lazy初始化与Eager初始化。不同的是,在第一个实例中,直到调用getInstance()方法才会创建实例,但在第二个实例中甚至在调用getInstance()方法之前就已经创建了实例。

如果您想了解更多信息,请参阅此链接

从单元测试的角度来看,我更喜欢懒惰的安装。考虑到singleton的初始化有进一步的副作用(与实际测试无关(,并且您想要测试一个需要singleton的类(可能只是一个特定的方法(,在准备测试时模拟singleton并将其注入实例变量会更容易。对您的singleton实例使用mock,您可以更容易地控制singleton的方法返回到测试中的类的内容。

线程安全实例化的开销可以通过双重检查的锁模式来最小化:

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"将返回false,并且您再也不会进入锁定状态。

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

注意:已经证明,上述"双重检查锁定"模式并非100%可靠。请参阅以下评论中的讨论,尤其是Brian Goetz的arcticle

最新更新