Java:懒惰初始化单例

时间:2011-04-30 14:08:20

标签: java design-patterns synchronization singleton

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

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

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

但是我的问题是,如果Singleton Constructor做了一些非单元测试友好的东西,你如何使用这样的类单元,例如调用外部服务,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中双重锁定模式的破坏本质。

5 个答案:

答案 0 :(得分:11)

您可以将枚举用作Singleton

enum Singleton {
    INSTANCE;
}

假设您的单身人士在单元测试中做了一些不受欢迎的事情,你可以;

// 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.class,则稍后用于初始化更改的值不会出现问题。


编辑:如果您只使用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

答案 1 :(得分:6)

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

或者,避免使用单例模式,而是使用Dependency Injection

答案 2 :(得分:4)

双重检查锁定在每种语言中都被破坏,而不仅仅是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()
  {
  }

  // ...
}

编辑:修改了参考文献。

答案 3 :(得分:2)

您可以依赖注入单例实例,从单元测试代码覆盖getInstance(),使用面向方面编程来拦截方法调用并返回不同的对象,或使用像jmockit这样的工具让你嘲笑几乎所有东西,包括静力学,最终类,构造函数,以及人们通常所说的“不可测试的东西”。

我在遗留系统中采用的一种方法(我希望在对系统架构影响最小的情况下制作可测试的东西)是修改工厂方法(getInstance)以检查系统属性以寻找替代实现实例化。这被设置为单元测试套件中的备用模拟对象。

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

答案 4 :(得分:0)

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

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

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