正确使用Singleton getInstance方法

时间:2016-11-01 20:02:36

标签: java singleton

使用静态同步getInstance方法的单例的正确用法是什么?为什么?

例如:

MySingleton mySingleton = MySingleton.getInstance();
.....
mySingleton.doSomething();

其中mySingleton将是一个字段并在整个班级中使用。

MySingleton.getInstance().doSomething();

编辑:正确地说,我的意思是编码风格和线程安全。

4 个答案:

答案 0 :(得分:1)

比使用单身人士更好的解决方案是使用dependency injection

class MyClass {
  private final MySingleton mySingleton;

  MyClass(MySingleton mySingleton) {
    this.mySingleton = mySingleton;
  }

  // Use mySingleton in instance methods as required.
}

这里的优势在于它与单身人士相关的事实并不相关 - 它可以是一个单身人士,但它并不需要。

我打电话给mySingleton字段,因为这是您在问题中所称的内容,但不再要求它真正成为单身人士:唯一的就是{ {1}}并不需要关心实例的生命周期。

这会破坏MyClassMyClass之间的静态耦合,如果您通过问题中描述的任何一种方式来实现它。这样可以更轻松地测试MySingleton,以及其他优势。

答案 1 :(得分:1)

有人必须实例化单身人士。这只需要进行一次。这里有线程安全发挥。如果有可能不同的线程调用

SingletonClass.getInstance();

几乎在同一时间,单身人士的实例化必须得到这样的保护:

private static volatile SingletonClass myInstance;

public static SingletonClass getInstance()
{
    if ( myInstance == null )
    {
        synchronized( SingletonClass.class)
        {
            if ( myInstance == null )
            {
                myInstance = new SingletonClass();
            }
        }
    }

    return myInstance;
}

这称为"双重检查锁定"。变量必须是易变的。

如果SingletonClass的方法在线程之间共享状态(例如,您有一个由一个线程编写并由另一个线程读取的成员),则必须特别注意同步。但这很常见,与Singleton模式无关。

您的问题" XY.getInstance()。doSomething()"之间的区别是什么?与保留自己成员中的引用以便以后访问它:它只是一个(次要的)性能损失(callstack还有一个,if语句,返回)。但IMO这是可以忽略的,因为从长远来看,java优化器无论如何都会内联这个。另一方面:保持引用可以使代码更短,更易读,特别是如果您经常访问单例。最后,这是一个品味问题。

答案 2 :(得分:1)

虽然我同意Andy Turner在可能的情况下避免单身人士的答案,但我会将其添加为完整性:静态同步的getInstance方法是过去的遗物。至少有3个单例模式实现不需要。

a)渴望单身人士

public class EagerSingleton{
  private EagerSingleton(){}
  private static final EagerSingleton INSTANCE = new EagerSingleton();
  public static EagerSingleton getInstance(){ return INSTANCE; }
}

无论何时以任何方式引用类EagerSingleton(例如,通过声明该类型的变量),都会在类加载时初始化。

b)懒惰的单身人士

public class LazySingleton{
    private LazySingleton(){}
    private static class Holder{
        private static final LazySingleton INSTANCE = new LazySingleton();
    }
    public static LazySingleton getInstance(){ return Holder.INSTANCE; }

}

这是在第一次调用方法getInstance()时在调用时初始化的。类加载器现在加载holder类并初始化INSTANCE字段,并保证以同步方式执行此操作。如果您的单身人士设置昂贵,请使用此版本,但并非总是如此。

c)enum Singleton

public enum EnumSingleton{
    INSTANCE;

    // and if you want, you can add the method, too, but it's
    // unnecessary:
    public static EnumSingleton getInstance(){ return INSTANCE; }
}

枚举项是编译时常量,即它们的唯一性在编译时得到保证,不仅在运行时(在运行时,单例对于每个类加载器都是唯一的,并且适用于所有版本)。这是最好的方法,除非您的要求是从现有的课程扩展。

enum版本免费提供许多其他功能:

  • 等于/ hashCode / toString实现开箱即用
  • 防范反序列化攻击
  • multiton support(只需添加另一个枚举项)

因此,下次当您看到某人编写双重检查锁定机制或类似情况时,请告诉他们这些机制过于复杂和技术性。让类加载器为您完成工作。

使用所有3个版本:确保您使用的功能由接口支持,并针对该接口提供代码,而不是实现类型。这将使您的代码可测试。

答案 3 :(得分:0)

我认为最好为班级中的每个单身人士定义一个字段。因此,只需查看前n行,就可以轻松地找到系统中单元的依赖关系。它有助于理解一个类的耦合。

此外,为单例定义字段使测试更容易。如果您有可能将单例的方法移动到接口,则可以为单例定义存根类,并且可以在测试代码中注入存根单例。

对于线程安全部分,如果你的单例是线程安全的,那么应该没有任何问题。