Java中简化的单例模式

时间:2010-03-06 00:19:40

标签: java design-patterns singleton

实现单例模式的默认方法是:

class MyClass {
  private static MyClass instance;
  public static MyClass getInstance() {
    if (instance == null) {
      instance = new MyClass();
    }
    return instance;
  }
}

在一个旧项目中,我试图简化写作:

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

但它有时会失败。我只是不知道为什么,我做了默认的方式。 让SSCCE今天在这里发布,我意识到代码可以运行。

所以,我想知道意见.. 这是一个随意的失败代码吗? 第二种方法是否有可能返回null? 我疯了吗?

- 虽然我不知道每个案例的答案是否正确,但@Alfred的回答非常有趣: 我还想指出,单身人士正在测试噩梦,根据大家伙的说法,你应该使用谷歌的依赖注入框架。

8 个答案:

答案 0 :(得分:8)

推荐(通过Effective Java 2nd ed)方式是执行“枚举单例模式”:

enum MyClass {
    INSTANCE;

    // rest of singleton goes here
}

这里的关键见解是枚举值是单实例,就像单例一样。因此,通过制作一个单值枚举,你只需要让自己成为一个单身人士。这种方法的优点在于它完全是线程安全的,并且它还可以安全地防止任何允许人们创建其他实例的漏洞。

答案 1 :(得分:4)

第一个解决方案是(我相信)不是线程安全的。

第二个解决方案(我相信)是线程安全的,但是如果您在MyClass.getInstance()静态初始化完成之前调用了MyClass的复杂初始化依赖关系,则可能无效。这可能是你看到的问题。

这两个解决方案都允许某人创建您的(名义上的)单例类的另一个实例。

更强大的解决方案是:

class MyClass {

  private static MyClass instance;

  private MyClass() { }

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

在现代JVM中,获取锁定的成本微乎其微,前提是锁定没有争用。

编辑 @Nate质疑我关于可能导致问题的静态初始化顺序的陈述。考虑以下(病理)示例:

public ClassA {
    public static ClassB myB = ClassB.getInstance();
    public static ClassA me = new ClassA();
    public static ClassA getInstance() {
        return me;
    }
}

public ClassB {
    public static ClassA myA = ClassA.getInstance();
    public static ClassB me = new ClassB();
    public static ClassB getInstance() {
        return me;
    }
}

这两个类有两种可能的初始化顺序。两者都导致在执行方法的类静态初始化之前调用静态方法。这会导致ClassA.myBClassB.myA初始化为null

实际上,静态之间的循环依赖关系不如此明显。但事实仍然是,如果存在循环依赖:1)Java编译器将无法告诉您它,2)JVM不会告诉您它。相反,JVM将默默地选择初始化顺序,而不“理解”您正在尝试执行的操作的语义...可能导致意外/错误。

编辑2 - JLS 12.4.1中对此进行了描述,如下所示:

  

如§8.3.2.3中的示例所示,初始化代码不受限制的事实允许构造示例,其中类变量的值在其初始化默认值之前可以被观察到,在其初始化表达式之前是评估,但这样的例子在实践中很少见。 (这些示例也可以构造为例如变量初始化;参见§12.5末尾的示例)。这些初始化器提供了该语言的全部功能;程序员必须小心谨慎。 ...

答案 2 :(得分:3)

第二个例子是可取的,因为第一个例子不是线程安全的(如评论中所指出的)。第一个示例使用一种称为lazy instantiation(或延迟初始化)的技术,该技术确保不会创建Singleton实例,除非实际需要它。由于Java处理类加载和静态实例变量初始化的方式,这在Java中并不是必需的。

答案 3 :(得分:2)

我还想指出singletons正在测试噩梦,根据大家伙的说法,你应该使用google的dependency injection framework

答案 4 :(得分:1)

请记住,您需要声明一个私有构造函数以确保单例属性。

第二种情况可能更简单,只需

class MyClass {
  public static final MyClass instance = new MyClass();

  private MyClass() {
      super()
  }
}

`

答案 5 :(得分:1)

正如其他人所说,第一个不是线程安全的。不要打扰它,因为第二个完全正常,并且只有在引用MyClass时才会实例化对象。此外,它使参考最终更好地表达了意图。

请确保声明

private static final MyClass INSTANCE = new MyClass();

是类中的第一个静态声明,用于避免在初始化之前调用getInstance()或INSTANCE的风险。

答案 6 :(得分:1)

不要忘记SingletonHolder模式。请参阅this SO问题。

答案 7 :(得分:0)

我不知道你的问题的答案,但这是我如何构建同样的事情。

class MyClass {
  private static MyClass instance;

  static {
    instance = new MyClass();
  }

  private MyClass() { }

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