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

时间:2013-09-17 16:45:00

标签: java multithreading design-patterns singleton

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

public class Singleton {

    private static Singleton ref;

    private Singleton() {
    }

    public static Singleton getInstance() {
        if (ref == null) {
            ref = new Singleton();
        }
        return ref;
    }
}
突然他告诉我这是写作课的老方法。任何人都可以帮助我,为什么他这样说。

13 个答案:

答案 0 :(得分:42)

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

enum Singleton {
    INSTANCE;
}

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

使用singleton类,您必须确保序列化和反序列化不会通过实现readResolve()方法创建新实例,而枚举则不是这样。

使用class你应该像这样创建单身:

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

答案 1 :(得分:14)

最新标准解决方案:

  • 使用Managed Beans / CDI的核心Java

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

    @Singleton
    public class MySingleton { ... }
    

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

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

  • 优点:

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

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

答案 2 :(得分:10)

你可以做到

public enum Singleton {
    INSTANCE;
}

以及没有实例的实用程序类

public enum Utility {
     ;

     public static void method();
}

答案 3 :(得分:8)

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

我们有以下形式的单身人士:

public enum Foo {
    INSTANCE;
}

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

public enum Foo {
    INSTANCE,
    ANOTHER;
}

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

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

修改

为了完整性,这里是一个枚举单例,用于防止以后添加的其他值:

public enum Foo
{
  INSTANCE;
  // adding another type here will cause a runtime

  static
  {
    if (Foo.values().length != 1)
    {
      throw new IllegalStateException("Not a Singleton.");
    }
  }
}

答案 4 :(得分: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;
        }
}

更多信息on Wikipedia

答案 5 :(得分:3)

他可能会寻找这个答案:

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

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

答案 6 :(得分:3)

我写的Singleton看起来像这样:

@Service
class PersonService {
    // implementation here
}

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

答案 7 :(得分: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);

}

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

答案 8 :(得分:3)

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

答案 9 :(得分:2)

来自What is an efficient way to implement a singleton pattern in Java?

使用枚举:

 public enum Foo 
 {
   INSTANCE;
 }

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

另请查看The better Java singleton pattern nowadays?

答案 10 :(得分:2)

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

答案 11 :(得分:2)

调用此单例实现,

  

延迟初始化

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

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

其他一些流行的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) {
         .............
    }
}

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

答案 12 :(得分:1)

可能是因为它没有使用“double-checked-locking”(正如其他人所说的那样),或者也可能是因为显然可以调用private constructor using 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<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) );

    }

}