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

时间:2020-03-05 18:55:29  来源:igfitidea点击:

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

解决方案

回答

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

回答

Wikipedia也有一些单例的示例,也使用Java。 Java 5实现看起来很完整,并且是线程安全的(应用了双重检查锁定)。

回答

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

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

如果我们真的需要一个,那么维基百科提供了正确实现单例的一个很好的例子。

回答

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

public class A {    

    private static final A INSTANCE = new A();

    private A() {}

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

回答

如果我们不需要延迟加载,则只需尝试

public class Singleton {
    private final static Singleton INSTANCE = new Singleton();

    private Singleton() {}

    public static Singleton getInstance() { return Singleton.INSTANCE; }

    protected Object clone() {
        throw new CloneNotSupportedException();
    }
}

如果我们希望延迟加载并且希望Singleton是线程安全的,请尝试仔细检查模式

public class Singleton {
        private static Singleton instance = null;

        private Singleton() {}

        public static Singleton getInstance() { 
              if(null == instance) {
                  synchronized(Singleton.class) {
                      if(null == instance) {
                          instance = new Singleton();
                      }
                  }
               }
               return instance;
        }

        protected Object clone() {
            throw new CloneNotSupportedException();
        }
}

由于不能保证双重检查模式有效(由于编译器的某些问题,我对此一无所知。),我们还可以尝试同步整个getInstance方法或者为所有Singleton创建一个注册表。

回答

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的答案详细介绍了Bill Pugh(FindBugs)建议的方法,并且可以说是更好的方法。去阅读并投票支持他的答案。

回答

使用一个枚举:

public enum Foo {
    INSTANCE;
}

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

The Right Way to Implement a Serializable Singleton

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

编辑:"有效Java"的在线部分说:

"This approach is functionally equivalent to the public field approach, except that it is more concise, provides the serialization machinery for free, and provides an ironclad guarantee against multiple instantiation, even in the face of sophisticated serialization or reflection attacks. While this approach has yet to be widely adopted, a single-element enum type is the best way to implement a singleton."

回答

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

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

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

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

回答

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

回答

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

由于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。然后创建一个"私有静态最终Foo"字段来保存唯一的实例,并创建一个"公共静态Foo getInstance()"方法来返回它。 Java规范确保仅在首次使用该类时才调用构造函数。

如果我们有一个很大的对象或者繁重的构造代码,并且还需要在需要实例之前使用其他可访问的静态方法或者字段,则仅在那时才需要使用惰性初始化。

我们可以使用"私有静态类"来加载实例。代码如下:

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

由于仅在实际使用类FooLoader时才执行" private static final Foo INSTANCE = new Foo();"行,因此,它可以处理惰性实例化,并且可以保证是线程安全的。

当我们还希望序列化对象时,需要确保反序列化不会创建副本。

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()方法将确保返回唯一的实例,即使该对象在程序的先前运行中已序列化也是如此。

回答

有时,简单的"静态Foo foo = new Foo();`"是不够的。只需考虑我们要执行的一些基本数据插入。

另一方面,我们将必须同步任何实例化此类单例变量的方法。同步本身还不错,但是它可能导致性能问题或者锁定(在非常罕见的情况下,使用此示例。解决方案是

public class Singleton {

    private static Singleton instance = null;

    static {
          instance = new Singleton();
          // do some of your instantiation stuff here
    }

    private Singleton() {
          if(instance!=null) {
                  throw new ErrorYouWant("Singleton double-instantiation, should never happen!");
          }
    }

    public static getSingleton() {
          return instance;
    }

}

现在会发生什么?该类是通过类加载器加载的。从字节数组解释了类之后,VM会立即执行静态{}块。这就是全部秘密:在此类加载器加载给定包的给定类(名称)时,只会调用一次static-block。

回答

别忘了Singleton只是加载它的Classloader的Singleton。如果我们使用多个装载机(容器),则每个COULD都有其自己的Singleton版本。

回答

我使用Spring框架来管理我的单身人士。它不会强制类的"单一性"(如果涉及多个类加载器,我们将无法真正做到),但是它提供了一种非常简单的方法来构建和配置用于创建不同类型对象的不同工厂。