java - java实现一个单例模式的有效方法是什么?

  显示原文与译文双语对照的内容

java实现一个单例模式的有效方法是什么?

时间:

使用枚举:


public enum Foo {
 INSTANCE;
}

约书亚·布洛赫解释这种方法在他 Effective Java 重载说在谷歌i/o 2008: 链接到视频列表。 他的演讲( effective_java_reloaded.pdf ) 也看到幻灯片 30 -32:

实现可以序列化Singleton的正确方法


public enum Elvis {
 INSTANCE;
 private final String[] favoriteSongs =
 {"Hound Dog","Heartbreak Hotel" };
 public void printFavorites() {
 System.out.println(Arrays.toString(favoriteSongs));
 }
}

编辑:在线部分"Effective Java"说:

"这种方法的功能相当于公共领域的方法,但它更简洁,免费提供序列化机制,与多个实例化提供了一个坚固的保障,即使在面对复杂的序列化或反射攻击。 虽然这种方法尚未广泛采用, single-elementenum类型是最好的方法来实现一个单例 。"

根据用法,有几个"正确"答案。

因为java5最好的方法是使用一个枚举:


public enum Foo {
 INSTANCE;
}

Pre java5,最简单的情况是:


public final class Foo {

 private static final Foo INSTANCE = new Foo();

 private Foo() {
 if (INSTANCE!= null) {
 throw new IllegalStateException("Already instantiated");
 }
 }

 public static Foo getInstance() {
 return INSTANCE;
 }
}

让我们检查一下代码。 首先,你希望类是 final 。 这种情况下,我使用了 final 关键字来让用户知道它是 final 。 然后你需要使构造函数私有,以防止用户创建自己的Foo 。 从构造函数抛出异常防止用户使用反射创建第二个 Foo 。 然后创建一个 private static final Foo 字段保存唯一的实例,并且 public static Foo getInstance() 方法返回它。java规范确保构造函数只是第一次使用类时调用。

当你有一个非常大的对象或重型建筑代码,也可能使用其他访问静态方法或字段之前需要一个实例,然后,只需要使用延迟初始化。

你可以使用 private static class 来加载实例。 然后代码看起来就像:


public final class Foo {

 private static class FooLoader {
 private static final Foo INSTANCE = new Foo();
 }

 private Foo() {
 if (FooLoader.INSTANCE!= null) {
 throw new IllegalStateException("Already instantiated");
 }
 }

 public static Foo getInstance() {
 return FooLoader.INSTANCE;
 }
}

因为这条线 private static final Foo INSTANCE = new Foo(); 仅在实际使用类FooLoader时执行,它负责惰性实例化,并且保证它是线程安全的。

当你还想序列化你的对象时,你需要确保反序列化不会创建副本。


public final class Foo implements Serializable {

 private static final long serialVersionUID = 1L;

 private static class FooLoader {
 private static final Foo INSTANCE = new Foo();
 }

 private Foo() {
 if (FooLoader.INSTANCE!= null) {
 throw new IllegalStateException("Already instantiated");
 }
 }

 public static Foo getInstance() {
 return FooLoader.INSTANCE;
 }

 @SuppressWarnings("unused")
 private Foo readResolve() {
 return FooLoader.INSTANCE;
 }
}

readResolve() 方法将确保唯一的实例将被返回,即使对象被序列化之前运行你的程序。

Stu 。汤普森发布的解决方案在 Java5.0 和更高版本中有效。 但我不想使用它,因为我认为它很容易出错。

很容易忘记volatile语句,很难理解为什么需要。 没有 volatile,由于double-checked锁定反模式,这里代码不再是线程安全的。 看到更多关于这款 16.2.4 java并发的实践。 简而言之:这个模式( 在 Java5.0 或者没有volatile语句之前) 可以返回一个对( 仍然仍然仍然仍然仍然仍然仍然仍然仍然仍然仍然仍然仍然仍然仍然仍然仍然仍然仍然仍然仍然仍然仍然仍然仍然仍然仍然仍然仍然仍然仍然仍然仍然仍然仍然仍然仍然仍然仍然仍然仍然仍然仍然仍然仍然仍然仍然仍然仍然仍然仍然仍然仍然仍然仍然仍然仍然仍然仍然仍然仍然仍然仍然仍然仍然仍然仍然仍然仍然仍然仍然仍然仍然仍然仍然仍然仍然仍然仍然仍然仍然仍然仍然仍然仍然仍然仍然仍然仍然仍然仍然仍然仍然仍然仍然仍然仍然仍然仍然仍然仍然) 对象的引用,它的状态不正确。

这里模式是为性能优化而发明的。 但这真的不是真正的问题。 下面的惰性初始化代码是快速的,-more importantly-更易于阅读。


class Bar {
 private static class BarHolder {
 public static Bar bar = new Bar();
 }

 public static Bar getBar() {
 return BarHolder.bar;
 }
}

Java 5 + 中的线程安全:


class Foo {
 private static volatile Bar bar = null;
 public static Bar getBar() {
 if (bar == null) {
 synchronized(Foo.class) {
 if (bar == null)
 bar = new Bar(); 
 }
 }
 return bar;
 }
}


编辑: 注意 volatile 修改器。 :它很重要,因为没有它,JMM ( Java内存模型) 就不能保证对它的值的更改。 同步并不照顾that--it只有序列化访问的代码块。

编辑 2: @Bno ( FindBugs ) 推荐的详细方法的回答。 去阅读并投票给他的答案。

忘记惰性初始化,它太问题了。 这是最简单的解决方案:


public class A { 

 private static final A INSTANCE = new A();

 private A() {}

 public static A getInstance() {
 return INSTANCE;
 }
}

免责声明:我刚刚总结了所有的可怕的答案,写在我的文字里。


在实现Singleton时,我们有 2个选项
1.延迟加载
2.早期加载

延迟加载添加位 overhead(lots of to be honest),所以只有当你有一个非常大的对象或者大量的构造代码时才使用它,而且还有其他可以访问的静态方法或者字段,这也是一个很好的选择,并且在需要早期加载时,你需要使用惰性的initialization.Otherwise 。

实现Singleton的最简单方法是


public class Foo {

//It will be our sole hero
 private static final Foo INSTANCE = new Foo();

 private Foo() {
 if (INSTANCE!= null) {
//SHOUT
 throw new IllegalStateException("Already instantiated");
 }
 }

 public static Foo getInstance() {
 return INSTANCE;
 }
}

除了它早期加载的singleton以外,一切都很好。 让我们尝试惰性加载的singleton


class Foo {

//Our now_null_but_going_to_be sole hero 
 private static Foo INSTANCE = null;

 private Foo() {
 if (INSTANCE!= null) {
//SHOUT 
 throw new IllegalStateException("Already instantiated");
 }
 }

 public static Foo getInstance() {
//Creating only when required.
 if (INSTANCE == null) {
 INSTANCE = new Foo();
 }
 return INSTANCE;
 }
}

到目前为止这么好,但是我们的英雄不会在战斗时生存,而这些邪恶线程需要许多我们英雄的实例。 所以让我们保护它免受多重线程的影响


class Foo {

 private static Foo INSTANCE = null;

//TODO Add private shouting constructor

 public static Foo getInstance() {
//No more tension of threads
 synchronized (Foo.class) {
 if (INSTANCE == null) {
 INSTANCE = new Foo();
 }
 }
 return INSTANCE;
 }
}

但是保护英雄是不够的 ! 这是我们能做的最好的事情来帮助我们的英雄


class Foo {

//Pay attention to volatile
 private static volatile Foo INSTANCE = null;

//TODO Add private shouting constructor

 public static Foo getInstance() {
 if (INSTANCE == null) {//Check 1
 synchronized (Foo.class) {
 if (INSTANCE == null) {//Check 2
 INSTANCE = new Foo();
 }
 }
 }
 return INSTANCE;
 }
}

这被称为"double-checked锁定习惯用法"。 很容易忘记volatile语句,很难理解为什么需要。
详细信息:http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html

现在我们确定了邪恶线程,但是残酷的序列化又如何? 即使de-serialiaztion没有创建新对象,我们也必须确保


class Foo implements Serializable {

 private static final long serialVersionUID = 1L;

 private static volatile Foo INSTANCE = null;

//Rest of the things are same as 上面

//No more fear of serialization
 @SuppressWarnings("unused")
 private Foo readResolve() {
 return INSTANCE;
 }
}

方法 readResolve() 将确保返回唯一的实例,即使该对象在我们的程序上一次运行时被序列化。

最后我们已经为线程和序列化添加了足够的保护,但是我们的代码看起来又笨重又丑陋。 让我们的英雄成为一个


public final class Foo implements Serializable {

 private static final long serialVersionUID = 1L;

//Wrapped in a inner static class so that loaded only when required
 private static class FooLoader {

//And no more fear of threads
 private static final Foo INSTANCE = new Foo();
 }

//TODO add private shouting construcor

 public static Foo getInstance() {
 return FooLoader.INSTANCE;
 }

//Damn you serialization
 @SuppressWarnings("unused")
 private Foo readResolve() {
 return FooLoader.INSTANCE;
 }
}

是的,这是我们的英雄:
因为这条线 private static final Foo INSTANCE = new Foo(); 仅在实际使用类 FooLoader 时执行,这将处理惰性实例化,

它是否保证是线程安全的。

我们到目前为止,这是最好的方法来实现我们所做的一切


 public enum Foo {
 INSTANCE;
 }

内部将被视为


public class Foo {

//It will be our sole hero
 private static final Foo INSTANCE = new Foo();
}

这就不再害怕序列化,线程和丑陋代码。 另外,的枚举单元素被延迟初始化为

这种方法在功能上等同于公共字段方法,除了它更加简洁,提供了序列化机制,并提供了对多重实例化,甚至面对复杂的序列化或者反射攻击的双重保障。 虽然这种方法还没有被广泛采用,但是single-element枚举类型是实现单一模式的最好方法。

-Joshua在"Effective Java 中的"

现在你可能已经意识到为什么枚举被认为是实现单例的最佳方式,并感谢你的耐心:
已经将它的更新为我的 博客

确保你真的需要它。 为"单一 anti-pattern"做一个来查看它的一些参数。 我认为这并没有什么固有的错误,但它只是一个公开一些全局资源/数据的机制,所以确保这是最好的方式。 特别是,我发现依赖注入更加有用,特别是如果你也使用单元测试,因为DI允许你使用mock资源进行测试。

不要忘了Singleton只是加载它的类加载器的一个单一实例。 如果你使用的是多个加载器( 容器),每个装载器都可以有它自己的单独版本。

我是mystified的一些回答,建议DI作为使用单例单例的替代品;这些是无关的概念。 你可以使用DI来注入singleton或者 non-singleton ( 例如。 per-thread ) 实例。如果你使用 spring 2. x,,我不能为其他的DI框架使用。

所以我对OP的回答是:

  1. 使用像 spring 这样的DI框架,然后
  2. 使它成为你的DI配置的一部分,无论你的依赖是单例,请求作用域,会话作用域还是任何其他的。

这种方法给你一个很好的解耦( 因此灵活可以测试) 架构,其中是否使用单例是一个易于实现的实现细节( 提供的任何单例都是线程安全的,当然) 。

真正考虑为什么在写它之前需要一个 singleton 。 有一个关于使用quasi-religious的争论,如果你在Java中使用单例模式,你可以很容易地发现它。

就我个人而言,我尽量避免单例尽可能经常的原因很多,其中大部分可以通过网络搜索单例对象被发现。 我觉得经常单例对象被虐待,因为他们被大家都容易理解,使用它们作为一种机制让"全局"数据转换为面向对象设计和使用它们,因为它很容易绕过( 或者你真的想怎么从里面做A ) 对象生命周期管理。 查看像反转控制( IoC ) 或者依赖注入( n 。DI ) 这样的漂亮 middleground 。

如果你真的需要一个,维基百科就有一个正确的实现例。

...