如何以适当的方式编写单身人士?
今天在我的采访中,一位采访者让我写了一个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) ); } }