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

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


使用枚举:

public enum Foo {
    INSTANCE;
}

Joshua Bloch在Google I / O 2008上的Effective Java Reloaded讲座中解释了这种方法:链接到视频。 另请参阅演示文稿的幻灯片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()确保返回唯一的实例,即使在程序的上一次运行中序列化对象时也是如此。


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

很容易忘记易变的陈述,并且很难理解为什么这是必要的。 没有易失性,由于双重检查锁定反模式,此代码不再是线程安全的。 请参阅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;
    }
}
链接地址: http://www.djcxy.com/p/13827.html

上一篇: What is an efficient way to implement a singleton pattern in Java?

下一篇: When does static class initialization happen?