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


当前回答

如果需要惰性地加载类的实例变量,则需要双重检查习惯用法。如果需要惰性地加载静态变量或单例,则需要按需初始化持有者习惯用法。

此外,如果单例需要可序列化,则所有其他字段都需要是暂时的,并且需要实现readResolve()方法以保持单例对象不变。否则,每次反序列化对象时,都会创建对象的新实例。readResolve()所做的是用readObject()替换读取的新对象,因为没有变量引用该对象,所以强制对该新对象进行垃圾回收。

public static final INSTANCE == ....
private Object readResolve() {
  return INSTANCE; // Original singleton instance.
} 

其他回答

这是如何实现一个简单的单例:

public class Singleton {
    // It must be static and final to prevent later modification
    private static final Singleton INSTANCE = new Singleton();
    /** The constructor must be private to prevent external instantiation */
    private Singleton(){}
    /** The public static method allowing to get the instance */
    public static Singleton getInstance() {
        return INSTANCE;
    }
}

这是如何正确地延迟创建单例:

public class Singleton {
    // The constructor must be private to prevent external instantiation
    private Singleton(){}
    /** The public static method allowing to get the instance */
    public static Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
    /**
     * The static inner class responsible for creating your instance only on demand,
     * because the static fields of a class are only initialized when the class
     * is explicitly called and a class initialization is synchronized such that only
     * one thread can perform it, this rule is also applicable to inner static class
     * So here INSTANCE will be created only when SingletonHolder.INSTANCE
     * will be called
     */
    private static class SingletonHolder {
        private static final Singleton INSTANCE = new Singleton();
    }
}

在写它之前,真的要考虑一下为什么你需要一个单例。关于使用它们有一个准宗教的争论,如果你在Java中搜索单例,你很容易就会发现。

就我个人而言,出于许多原因,我尽量避免单身汉,其中大部分可以通过谷歌搜索单身汉找到。我觉得单身汉经常被虐待,因为他们很容易被每个人理解。它们被用作将“全局”数据引入OO设计的机制,因为它很容易绕过对象生命周期管理(或者真正思考如何从B内部实现a)。看看像控制反转(IoC)或依赖注入(DI)这样的事情,可以找到一个不错的中间地带。

如果你真的需要一个,那么Wikipedia有一个很好的例子来说明单例的正确实现。

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


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

这被称为“双重检查锁定习惯用法”。人们很容易忘记不稳定的说法,也很难理解为什么有必要这样做。详细信息:“双重检查锁定失败”声明

现在我们确定了邪恶的线索,但残酷的连载呢?我们必须确保即使在反序列化时也不会创建新对象:

class Foo implements Serializable {

    private static final long serialVersionUID = 1L;

    private static volatile Foo INSTANCE = null;

    // The rest of the things are same as above

    // No more fear of serialization
    @SuppressWarnings("unused")
    private Object 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();
}

就是这样!不再担心序列化、线程和丑陋的代码。ENUMS单例也被延迟初始化。

该方法在功能上等同于公共领域方法,除了它更简洁,提供了序列化机制免费提供,并提供多重保险实例化,即使面对复杂的序列化或反射攻击。虽然这种方法尚未被广泛采用,单个元素枚举类型是实现单例的最佳方式。

-《有效的Java》中的约书亚·布洛赫

现在您可能已经意识到为什么ENUMS被认为是实现单例的最佳方式,并感谢您的耐心:)

在我的博客上更新了它。

维基百科有一些单态的例子,也在Java中。Java5实现看起来非常完整,并且是线程安全的(应用了双重检查锁定)。

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

注意此处的挥发性修饰语。:)这很重要,因为如果没有它,JMM(Java内存模型)就不能保证其他线程看到其值的更改。同步不会处理这一点——它只序列化对该代码块的访问。

@Bno的回答详细介绍了Bill Pugh(FindBugs)推荐的方法,并且可以更好地进行论证。去阅读并投票支持他的答案。