Java:懒惰初始化单例

创建单身人士的模式似乎是这样的:

public class Singleton { private static final Singleton instance = new Singleton(); private Singleton(){ } public static Singleton getInstance() { return instance; } } 

但是我的问题是,如果Singleton构造函数执行不是unit testing友好的事情,例如调用外部服务,jndi查找等,你如何使用这样的类。

我想我可以重构它:

 public class Singleton { private static Singleton instance; private Singleton(){ } public synchronized static Singleton getInstance() { if(instance == null) instance = new Singleton(); return instance; } //for the unit tests public static void setInstance(Singleton s) { instancce = s; } } 

现在的问题是,仅仅为了单元可测试性,我已经强制getInstance被同步,因此只是对于测试方面,它将对实际应用程序产生负面影响。 有没有办法解决它,似乎任何其他类型的延迟初始化将无法工作,因为java中的双重锁定模式的破坏性质。

您可以使用Factory模式创建单例,并根据环境切换实现。

或者,避免使用单例模式,而是使用dependency injection 。

您可以将枚举用作单身人士

 enum Singleton { INSTANCE; } 

假设您的单身人士在unit testing中做了不受欢迎的事情,你可以;

 // in the unit test before using the Singleton, or any other global flag. System.setProperty("unit.testing", "true"); Singleton.INSTANCE.doSomething(); enum Singleton { INSTANCE; { if(Boolean.getBoolean("unit.testing")) { // is unit testing. } else { // normal operation. } } } 

注意:不需要同步块或显式锁定。 在访问.class之前不会加载INSTANCE,并且在使用成员之前不会初始化。 如果您只使用Singleton.INSTANCE而不使用Singleton.INSTANCE ,则稍后用于初始化更改的值不会出现问题。


编辑:如果你只使用Singleton.class这可能不会初始化类。 它不在Java 8更新112的这个例子中。

 public class ClassInitMain { public static void main(String[] args) { System.out.println("Printing a class reference"); Class clazz = Singleton.class; System.out.println("clazz = " + clazz); System.out.println("\nUsing an enum value"); Singleton instance = Singleton.INSTANCE; } static enum Singleton { INSTANCE; Singleton() { System.out.println(getClass() + " initialised"); } } } 

版画

 Printing a class reference clazz = class ClassInitMain$Singleton Using an enum value class ClassInitMain$Singleton initialised 

双重检查锁定在每种语言中都被打破,而不仅仅是Java。

我倾向于避开单身,但如果你需要它们,你可以使用持有人模式,正如Josh Bloch的Effective Java中所推荐的那样:

 public class Foo { static class Holder { static final Foo instance = new Foo(); } public static Foo getInstance() { return Holder.instance; } private Foo() { } // ... } 

编辑:修复参考。

您可以dependency injection单例实例,从unit testing代码覆盖getInstance(),使用面向方面编程来拦截方法调用并返回不同的对象,或者使用像jmockit这样的工具,它可以让你模拟任何东西,包括静态,最后的阶级,构造者,以及人们通常所说的所有东西都是“不可测试的”。

我在遗留系统中采用的一种方法(我希望在对系统体系结构产生最小影响的情况下进行测试)是修改工厂方法(getInstance)以检查系统属性以寻找我将实例化的替代实现。 这被设置为unit testing套件中的备用模拟对象。

至于“双重检查锁定被破坏”语句,如果你使用volatile关键字,并且Java> = 1.5,那就不再那么真了。 它在1.4及更早版本中被破坏了(即使是使用volatile),但是如果你知道你的代码只能运行在最近的JVM上,我就不用担心了。 但我也不会使用单例:使用DI / IOC容器管理对象的生命周期可以更加优雅地解决您的两个问题(可测试性和同步访问器瓶颈)。

在执行unit testing的构建阶段,你如何懒惰初始化。 然后在编译分发之前将代码更改回内联初始化。

除了在测试期间,您的生产代码是内联初始化的。 也许这种差异btw生产和测试代码可能会引入错误,但是哪个?

(当然,如果这是一个解决方案,我们让构建阶段+工具完成工作。我看到这有助于maven和dp4j )。