如何以适当的方式编写单身人士?

今天在我的采访中,一位采访者让我写了一个Singleton课程。 我给出了答案

public class Singleton { private static Singleton ref; private Singleton() { } public static Singleton getInstance() { if (ref == null) { ref = new Singleton(); } return ref; } } 

突然他告诉我这是写作课的老方法。 任何人都可以帮助我,为什么他这样说。

在创建单例时,我首先想到的是enum 。 我通常使用枚举来实现单例:

 enum Singleton { INSTANCE; } 

使用枚举获得的一个好处是使用序列化。

对于singleton类,您必须确保序列化和反序列化不通过实现readResolve()方法创建新实例,而枚举不是这种情况。

使用类,您应该像这样创建单例:

 public final class Singleton implements Serializable { // For lazy-laoding (if only you want) private static class SingletonHolder { private static final Singleton INSTANCE = new Singleton(); } private Singleton() { if (SingletonHolder.INSTANCE != null) { // throw Some Exception } } public static Singleton getInstance() { return SingletonHolder.INSTANCE; } // To avoid deserialization create new instance @SuppressWarnings("unused") private Singleton readResolve() { return SingletonHolder.INSTANCE; } } 

最新标准方案:

  • 核心java与Managed Beans / CDI

     @ApplicationScoped public class MySingleton { ... } 
  • EJB Lite(JEE6)

     @Singleton public class MySingleton { ... } 

先前的建议(来自’Effective Java 2’):

  • 使用枚举,具有单个虚假枚举常量,例如INSTANCE。 数据字段和方法可以是静态的或非静态的(实例) – 两者的行为相同,因为只有一个实例

  • 优点:

    • 是的,这是一个单身人士。 不可能通过任何机制创建多个实例(在任何JVM上的任何给定类加载器中)。
    • 单例初始化是线程安全的。
  • 缺点(与上述标准解决方案相比):

    • 定义有点迟钝 – ‘enum’shell可能会误导缺乏经验的代码维护者。 这是一个小小的黑客,而不是枚举的初衷。
    • 实现通过抽象泄漏。 它违反了统一访问原则
      • 它不能通过注入工作 – 客户端必须知道它是针对精确枚举实例的单例和代码
      • 客户端必须使用Singleton.INSTANCE.someMethod()
      • 客户端不能(简单地)对接口进行编码
      • 受到设计影响的客户端在单例与/从多实例对象之间发生变化
    • 扩展祖先类(Enum),防止从其他类inheritance
    • 序列化和反序列化只是在没有状态的情况下传递枚举常量名称 – 这在技术上确保只有一个实例,但对数据有空操作。 在(罕见)事件中,想要跨JVM /类加载器共享/同步单例状态,无法使用通过序列化进行复制。

你可以做

 public enum Singleton { INSTANCE; } 

对于没有实例的实用程序类

 public enum Utility { ; public static void method(); } 

正如其他人已经指出的那样,enum模式现在被广泛认为是Singleton与老派方法的更好方法,但我只是想指出一个缺点。

我们有一个Singletonforms:

 public enum Foo { INSTANCE; } 

已经存在了一段时间,工作得很好。 然后在代码审查期间,我们看到了:

 public enum Foo { INSTANCE, ANOTHER; } 

在我们用湿鲭鱼击打他的脸后,有问题的编码器看到了他的方式的错误,并且必须退出和/或重写大量的代码。 是的,我们在它投入生产之前就把它抓住了,但是必须做好工作来消除它。

我觉得这种类型的单身人士的弱点(虽然很小,也许很少见)与老派的方式相比。 是的,你可以通过错误地实现它来打破任何模式,但对于编码人员而言,打破辛格尔顿的枚举似乎要比形成良好的老派单身人士更容易。

编辑:

为了完整起见,这里是一个枚举Singleton,可以防止以后添加的其他值:

 public enum Foo { INSTANCE; // adding another type here will cause a runtime static { if (Foo.values().length != 1) { throw new IllegalStateException("Not a Singleton."); } } } 

这是因为您的解决方案不是线程安全的。

现代方法是将实例绑定到enum值:

 enum Singleton { INSTANCE; } 

如果你想使用实例的延迟初始化,那么你可以使用ClassLoader来保证线程安全:

 public class Singleton { private Singleton() { } private static class SingletonHolder { public static final Singleton INSTANCE = new Singleton(); } public static Singleton getInstance() { return SingletonHolder.INSTANCE; } } 

有关维基百科的更多信息

他可能会寻找这个答案:

 public class Singleton { private static Singleton ref; static { ref = new Singleton(); } private Singleton() { } public static Singleton getInstance() { return ref; } } 

注意静态块。 这种方法可能很重,因为实例是在类加载时创建的。

我写的Singleton看起来像这样:

 @Service class PersonService { // implementation here } 

但我也喜欢enum的想法。 实际上,除了上面的那个之外,我从不写(也不需要)一个单身人士。

你为什么不能这样做

 public class SingletonSandBox { private static SingletonSandBox instance = new SingletonSandBox(); private SingletonSandBox(){ } public static SingletonSandBox getInstance(){ return instance; } } 

并测试

 public static void main(String[] args) { SingletonSandBox sss1 = SingletonSandBox.getInstance(); SingletonSandBox sss2 = SingletonSandBox.getInstance(); System.out.println(sss1 == sss2); } 

据我所知,这是线程安全的,比使用静态块更短。 再次,静态字段声明与运行时的静态块相比较。

有时我们可能只需创建一个子类的实例,就像Java Toolkit类一样。 示例1:仅创建子类的一个实例将详细说明它。 请参阅: Java中的Singleton Design Patter

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

使用枚举:

  public enum Foo { INSTANCE; } 

Joshua Bloch在他的“Effective Java”一书中解释了这种方法

还查看现在更好的Java单例模式?

OPs初始方法的线程安全版本,加上没有其他人敢于建议同步语句。

 final class Singleton { private static Object lock = new Object(); private static volatile Singleton instance = null; private Singleton() { } public static Singleton getInstance() { if(instance == null) { synchronized(lock) { if(instance == null) { instance = new Singleton(); } } } return instance; } } 

这个单例实现被调用,

懒惰的初始化

但问题是这个实现不是线程安全的。

在这里 ,您可以找到最佳的线程安全实现。

还有一些其他流行的Singleton实现。 一个是,

急切的初始化

 final class EagerIntializedSingleton { private static final EagerIntializedSingleton instance = new EagerIntializedSingleton(); private EagerIntializedSingleton (){} private static EagerIntializedSingleton getInsance() { return instance; } } 

但是在这里,单例类的实例是在类加载时创建的。 (这是IntelliJ IDE创建的默认单例类)

下一个流行的实现是,

静态块初始化

 private static StaticBlockSingleton instance; private StaticBlockSingleton(){} static { try { instance = new StaticBlockSingleton(); catch(Exception e) { ............. } } 

此实现类似于急切初始化,除了在提供“exception处理”选项的静态块中创建类的实例。 急切初始化和静态块初始化都会在使用之前创建实例,这不是最佳实践。

这可能是因为它没有使用“ 双重检查锁定 ”(正如其他人所说的那样),或者也可能是因为显然可以使用reflection来调用私有构造函数 (如果安全策略允许的话)。

调用没有参数的构造函数传递一个空数组。

 package org.example; public class Singleton { private static final Object LOCK = new Object(); private static final Singleton SINGLETON = new Singleton(); private static volatile boolean init = false; // 'volatile' to prevent threads from caching state locally (prevent optimizing) private Singleton() { synchronized (LOCK) { if( init == true) { throw new RuntimeException("This is a singleton class!"); } init=true; } } public static Singleton obtainClassInstance() { return SINGLETON; } } package org.example; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; public class SimpleSingletonTester { /** * @param args * @throws NoSuchMethodException * @throws SecurityException * @throws InvocationTargetException * @throws IllegalAccessException * @throws InstantiationException * @throws IllegalArgumentException */ public static void main(String[] args) throws SecurityException, NoSuchMethodException, IllegalArgumentException, InstantiationException, IllegalAccessException, InvocationTargetException { Class[] parameterTypes = {}; Object[] initargs = {}; Constructor constructor = Singleton.class.getDeclaredConstructor(parameterTypes); System.out.println( constructor.isAccessible() ); constructor.setAccessible(true); System.out.println( constructor.isAccessible() ); System.out.println( constructor.newInstance(initargs) ); System.out.println( constructor.newInstance(initargs) ); } }