在 Java 中实现单例模式的有效方法是什么?

在 Java 中实现单例模式的有效方法是什么?

答案

使用一个枚举:

public enum Foo {
    INSTANCE;
}

约书亚 · 布洛赫(Joshua Bloch)在 Google I / O 2008 上的 “ 有效的 Java 重新加载”演讲中解释了这种方法: 链接到视频 。另见幻灯片他的介绍(30-32 effective_java_reloaded.pdf ):

实现可序列化单例的正确方法

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

编辑: “有效 Java”在线部分说:

“该方法在功能上等同于公共领域方法,除了它更简洁,免费提供序列化机制,甚至在面对复杂的序列化或反射攻击时也提供了针对多重实例化的明确保证。尚未被广泛采用的单元素枚举类型是实现单例的最佳方法 。”

根据用法,有几个 “正确” 的答案。

由于 java5 的最佳方法是使用枚举:

public enum Foo {
   INSTANCE;
}

在 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;
    }

    public Object clone() throws CloneNotSupportedException{
        throw new CloneNotSupportedException("Cannot clone instance of this class");
    }
}

让我们来看一下代码。首先,您希望课程是最终的。在这种情况下,我使用了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()方法readResolve()确保返回唯一实例,即使该对象在程序的先前运行中已被序列化也是如此。

免责声明:我刚刚总结了所有很棒的答案,并用我的话写下来。


在实施 Singleton 时,我们有 2 个选择
1. 延迟加载
2. 提早加载

延迟加载会增加一些位开销(说实话,很多),因此仅当您有非常大的对象或繁重的构造代码并且还需要在需要实例之前使用其他可访问的静态方法或字段时才使用它您需要使用延迟初始化。否则,选择早期加载是一个不错的选择。

实现 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;
    }
}

除了早期加载的单例外,其他一切都很好。让我们尝试延迟加载的单例

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;
    }
}

这称为 “双重检查锁定习惯用法”。容易忘记易失性陈述,也很难理解为什么必须这样做。
有关详细信息: http : //www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html

现在我们可以确定邪恶线程,但是残酷的序列化又如何呢?我们必须确保即使反序列化也不会创建新对象

class Foo implements Serializable {

    private static final long serialVersionUID = 1L;

    private static volatile Foo INSTANCE = null;

    // Rest of the things are same as above

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

readResolve()方法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();
}

而已!不再担心序列化,线程和丑陋的代码。 ENUMS 单例也被延迟初始化

此方法在功能上与公共领域方法等效,除了它更简洁,免费提供序列化机制,甚至针对复杂的序列化或反射攻击,还提供了针对多重实例化的明确保证。尽管此方法尚未得到广泛采用,但是单元素枚举类型是实现单例的最佳方法。

-《有效 Java》中的约书亚 · 布洛赫(Joshua Bloch)

现在您可能已经意识到为什么将 ENUMS 视为实现 Singleton 的最佳方法了,感谢您的耐心配合:)
在我的博客上更新了它。

Stu Thompson 发布的解决方案在 Java5.0 及更高版本中有效。但是我不希望使用它,因为我认为它容易出错。

容易忘记易失性陈述,也很难理解为什么必须这样做。如果没有 volatile,由于经过了双重检查的锁定反模式,该代码将不再是线程安全的。有关更多信息,请参见《 Java 并发实践》第 16.2.4 段。简而言之:这种模式(在 Java5.0 之前或没有 volatile 语句)可能返回对(仍然)处于错误状态的 Bar 对象的引用。

发明这种模式是为了优化性能。但这真的不再是真正的问题。以下惰性初始化代码快速而且更重要的是更易于阅读。

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 内存模型)将无法保证其他线程无法看到其值的更改。同步并不会解决这个问题,它只会序列化对该代码块的访问。

编辑 2 :@Bno 的答案详细说明了比尔 · 普格(FindBugs)建议的方法,并且可以说是更好的方法。去阅读并投票支持他的答案。

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

public class A {    

    private static final A INSTANCE = new A();

    private A() {}

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

确保您确实需要它。用谷歌搜索 “单反模式”,看看反对它的论点。我想这没有天生的错误,但这只是公开某些全局资源 / 数据的一种机制,因此请确保这是最好的方法。特别是,我发现依赖注入更有用,特别是在您还使用单元测试的情况下,因为 DI 允许您将模拟资源用于测试目的。

我对某些答案感到迷惑不解,这些答案建议使用 DI 代替使用单例。这些是不相关的概念。您可以使用 DI 注入单例或非单例(例如,每线程)实例。至少如果您使用 Spring 2.x,这是正确的,我不能代表其他 DI 框架。

因此,我对 OP 的回答将是(除了最琐碎的示例代码之外):

  1. 使用像 Spring 这样的 DI 框架,然后
  2. 无论您的依赖项是单例,请求范围,会话范围还是任何其他类型,都将其作为 DI 配置的一部分。

这种方法为您提供了一个很好的解耦(因此是灵活且可测试的)体系结构,其中是否使用单例是一个易于逆转的实现细节(前提是您使用的任何单例都是线程安全的)。

真正考虑一下为什么在编写之前需要单身人士。关于使用它们的准宗教辩论,如果您用 Java 搜索单身人士,就很容易绊倒。

就我个人而言,出于多种原因,我会尽量避免单身人士,而再次通过搜索单身人士可以找到大多数原因。我觉得很多时候滥用单例是因为每个人都容易理解它们,它们被用作将 “全局” 数据输入到 OO 设计中的一种机制,并且因为它们很容易规避对象生命周期管理而被使用(或者真正在考虑如何从 B 内部做 A)。查看诸如控制反转(IoC)或依赖项注入(DI)之类的东西可以找到一个很好的中间立场。

如果您真的需要一个,那么维基百科就是正确实现单例的一个很好的例子。

以下是 3 种不同的方法

1)枚举

/**
* Singleton pattern example using Java Enumj
*/
public enum EasySingleton{
    INSTANCE;
}

2)仔细检查锁定 / 延迟加载

/**
* Singleton pattern example with Double checked Locking
*/
public class DoubleCheckedLockingSingleton{
     private static volatile DoubleCheckedLockingSingleton INSTANCE;

     private DoubleCheckedLockingSingleton(){}

     public static DoubleCheckedLockingSingleton getInstance(){
         if(INSTANCE == null){
            synchronized(DoubleCheckedLockingSingleton.class){
                //double checking Singleton instance
                if(INSTANCE == null){
                    INSTANCE = new DoubleCheckedLockingSingleton();
                }
            }
         }
         return INSTANCE;
     }
}

3)静态工厂方法

/**
* Singleton pattern example with static factory method
*/

public class Singleton{
    //initailzed during class loading
    private static final Singleton INSTANCE = new Singleton();

    //to prevent creating another instance of Singleton
    private Singleton(){}

    public static Singleton getSingleton(){
        return INSTANCE;
    }
}