24

今天在我的面试中,一位面试官让我写一门单例课程。我给了我的答案

public class Singleton {

    private static Singleton ref;

    private Singleton() {
    }

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

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

4

13 回答 13

42

创建单例时,我首先想到的是enum. 我一般使用枚举来实现单例:

enum Singleton {
    INSTANCE;
}

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

对于单例类,您必须确保序列化和反序列化不会通过实现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;
    }
}
于 2013-09-17T16:47:02.093 回答
15

最新标准解决方案:

  • 带有托管 Beans / CDI 的核心 java

    @ApplicationScoped
    public class MySingleton { ... }
    
  • EJB 精简版 (JEE6)

    @Singleton
    public class MySingleton { ... }
    

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

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

  • 好处:

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

    • 这个定义有点迟钝——“枚举”外壳可能会误导没有经验的代码维护者。这是一个小小的hack,并不是enum的初衷。
    • 实现通过抽象泄漏。它违反了 统一访问原则
      • 它不能通过注入工作 - 客户端必须知道它是一个单例和针对精确枚举实例的代码
      • 客户端必须使用 Singleton.INSTANCE.someMethod()
      • 客户端不能(简单地)针对接口编写代码
      • 客户受单例与多实例对象之间的设计更改的影响
    • 扩展一个祖先类(枚举),防止从其他类继承
    • 序列化和反序列化只是传输没有状态的枚举常量名称——这有助于在技术上确保只有一个实例,但对数据进行空操作。在(罕见的)事件中,想要在 JVM/类加载器之间共享/同步单例状态,无法使用通过序列化进行的复制。
于 2013-11-14T03:06:08.913 回答
10

你可以做

public enum Singleton {
    INSTANCE;
}

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

public enum Utility {
     ;

     public static void method();
}
于 2013-09-17T16:47:08.817 回答
8

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

我们有一个 Singleton 的形式:

public enum Foo {
    INSTANCE;
}

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

public enum Foo {
    INSTANCE,
    ANOTHER;
}

在我们用湿鲭鱼打了他的脸后,有问题的编码员已经看到了他的方式的错误,并且不得不撤销和/或重写大量的代码。是的,我们在它投入生产之前就捕捉到了它,但必须做一些工作才能将其擦除。

我觉得这是这种类型的 Singleton(尽管很小而且可能很少见)与老式方式相比的弱点。是的,您可以通过错误地实现任何模式来破坏任何模式,但是对于编码人员来说,破坏枚举 Singelton 似乎比格式良好的老式 Singleton 容易得多。

编辑:

为了完整起见,这里有一个枚举 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.");
    }
  }
}
于 2013-09-17T17:27:27.407 回答
3

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

现代方法是将实例绑定到一个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;
        }
}

有关维基百科的更多信息

于 2013-09-17T16:49:49.047 回答
3

他可能正在寻找这个答案:

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

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

于 2013-09-17T16:50:17.923 回答
3

我写的 Singleton 看起来像这样:

@Service
class PersonService {
    // implementation here
}

但我也喜欢枚举的想法。实际上,我从不写(也不需要)一个像上面那样的单例。

于 2013-09-17T16:51:16.820 回答
3

为什么你不能这样做

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

}

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

于 2013-11-19T00:33:22.060 回答
3

有时我们可能只需要创建一个子类的实例,就像 Java Toolkit 类一样。示例 1:仅创建子类的一个实例将详细说明它。请参考:Java 中的单例设计模式

于 2016-06-06T19:22:34.717 回答
2

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

使用枚举:

 public enum Foo 
 {
   INSTANCE;
 }

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

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

于 2013-09-17T16:54:49.227 回答
2

OP 初始方法的线程安全版本,再加上没有人敢提出同步语句。

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;
    }
}
于 2013-11-19T18:47:55.227 回答
2

这个单例实现被称为,

延迟初始化

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

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

也很少有其他流行的 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) {
         .............
    }
}

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

于 2017-03-24T08:56:09.373 回答
1

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

要调用不带参数的构造函数,请传递一个空数组。

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<Singleton> 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) );

    }

}
于 2013-11-19T19:24:44.797 回答