单例模式看上去是一个非常简单的设计模式,但是当涉及到实现时,它会涉及到很多问题。Singleton模式的实施,一直是开发者之间一个有争议的话题。在这里,我们将了解Singleton设计模式的原则,不同的方法来实现Singleton和一些最佳实践为它的用法。
单例设计模式:
为了实现Singleton模式,我们有不同的做法,但他们都有以下共同的规则。
1,private修饰构造方法,限制被其它的类实例化
2,Private static修饰类实例对象,保证这个类只有一个实例对象
3,Public static方法用于返回类实例变量,用于提供唯一入口来获取单例类的实例。
饿汉式(eager initialization)实现:
实例化单例对象在类加载时创建,这是一种很简单实现方法,但是缺点是当客户端创建该实例时可能并不使用它。
下面是静态初始化单例实现一种方式
EagerInitializedSingleton.java
package com.journaldev.singleton; public class EagerInitializedSingleton { private static final EagerInitializedSingleton instance = new EagerInitializedSingleton(); //private constructor to avoid client applications to use constructor private EagerInitializedSingleton(){} public static EagerInitializedSingleton getInstance(){ return instance; } }
如果这个单例类并不占有太多的资源,那这种实现可以使用。但在大多数情况下,当单例类涉及到如文件系统,数据库连接等资源时,我们应该避免其直接实例化,除非客户端调用getInstance方法,而且这种方法也没有提供对异常处理的任何选项。
静态代码快初始化(Static block initialization):
静态代码快初始化和上面一样一样,只不过多了异常处理
StaticBlockSingleton.java
package com.journaldev.singleton; public class StaticBlockSingleton { private static StaticBlockSingleton instance; private StaticBlockSingleton(){} //static block initialization for exception handling static{ try{ instance = new StaticBlockSingleton(); }catch(Exception e){ throw new RuntimeException("Exception occured in creating singleton instance"); } } public static StaticBlockSingleton getInstance(){ return instance; } }
即使通过饿汉初始化和静态代码块初始化实现实例被使用了,那也不是最好的实现方式,因此,在进一步的章节中,我们将学习如何创建一个支持延迟初始化Singleton类。
懒汉式单例(Lazy Initialization)实现:
懒汉式初始化单例当程序访问唯一一个创建实例方法时,实例才被创建。下面是这种方法的创建Singleton类的示例代码。
LazyInitializedSingleton.java
package com.journaldev.singleton; public class LazyInitializedSingleton { private static LazyInitializedSingleton instance; private LazyInitializedSingleton(){} public static LazyInitializedSingleton getInstance(){ if(instance == null){ instance = new LazyInitializedSingleton(); } return instance; } }
在单个线程环境下,上述实现方法没有问题,但如果是多线程系统中,当多个线程在同一时间,同时访问该方法时会产生一个问题,它会破坏Singleton模式和两个线程会得到singleton类的不同实例。在下一节中,我们将看到不同的方法来创建一个线程安全的单例类。
线程安全(Thread Safe Singleton)单例:
更简单的方法来创建一个线程安全的单例类是在创建实例方法前加synchroonized,在同一时间只有唯一的线程能够访问该方法,一般的实现是这样的:
ThreadSafeSingleton.java
package com.journaldev.singleton; public class ThreadSafeSingleton { private static ThreadSafeSingleton instance; private ThreadSafeSingleton(){} public static synchronized ThreadSafeSingleton getInstance(){ if(instance == null){ instance = new ThreadSafeSingleton(); } return instance; } }
上述实现工作正常,并提供了线程安全性,但它减少效率因为使用同步方法,虽然我们需要它仅适用于一个线程数可能创建独立的情况下使用,为了避免这种额外的开销,每次,双重检查锁定原则使用。在这种方法中,synchronized块是用来if条件里面有一个额外的检查,以确保创建单独的类只有一个实例。
下面的代码片段提供了双重检查锁定的实现。
public static ThreadSafeSingleton getInstanceUsingDoubleLocking(){ if(instance == null){ synchronized (ThreadSafeSingleton.class) { if(instance == null){ instance = new ThreadSafeSingleton(); } } } return instance; }
比尔普格(Bill Pugh Singleton)单例实现:
在此之前的Java5,Java内存模型有很多的问题和上面的方法用在某些场景下太多的线程试图同时得到Singleton类的实例失败。所以Bill Pugh想出了不同的方法来创建使用内部静态辅助类的Singleton类。
BillPughSingleton.java
package com.journaldev.singleton; public class BillPughSingleton { private BillPughSingleton(){} private static class SingletonHelper{ private static final BillPughSingleton INSTANCE = new BillPughSingleton(); } public static BillPughSingleton getInstance(){ return SingletonHelper.INSTANCE; } }
请注意,包含单例类实例的私有内部静态类。当单例类被加载,SingletonHelper类没有加载到内存中,只有当有人调用getInstance方法,这个类被加载,并创建Singleton类的实例。这是最广泛使用的方法,因为它不要求同步的单例类。在我的很多项目,我的使用这种做法做的,很容易理解与实现。
反射可以毁掉上述所有单例的实现,让我们看个例子:
ReflectionSingletonTest.java
package com.journaldev.singleton; import java.lang.reflect.Constructor; public class ReflectionSingletonTest { public static void main(String[] args) { EagerInitializedSingleton instanceOne = EagerInitializedSingleton.getInstance(); EagerInitializedSingleton instanceTwo = null; try { Constructor[] constructors = EagerInitializedSingleton.class.getDeclaredConstructors(); for (Constructor constructor : constructors) { //Below code will destroy the singleton pattern constructor.setAccessible(true); instanceTwo = (EagerInitializedSingleton) constructor.newInstance(); break; } } catch (Exception e) { e.printStackTrace(); } System.out.println(instanceOne.hashCode()); System.out.println(instanceTwo.hashCode()); } }
当你运行上面的测试类,你会发现两者的hashCode的情况并不相同,摧毁Singleton模式。反射是非常强大的,在很多像Spring和Hibernate框架的使用。
枚举单例:
为了克服反射这种情况,约书亚布洛赫建议使用枚举来实现Singleton设计模式,Java可以确保任何枚举值在Java程序中实例化一次。由于Java的枚举值是全局可访问的,所以是单例。其缺点是,枚举类型是有点不灵活;例如,它不允许延迟初始化(懒汉实现方式)。
package com.journaldev.singleton; public enum EnumSingleton { INSTANCE; public static void doSomething(){ //do something } }
序列化与单例:
有时,在分布式系统中,我们需要实现Singleton类Serializable接口,这样我们就可以将其存储的状态在文件系统,并在之后的时间点恢复。这里是一个实现Serializable接口也是一个小单例类。
SerializedSingleton.java
package com.journaldev.singleton; import java.io.Serializable; public class SerializedSingleton implements Serializable{ private static final long serialVersionUID = -7604766932017737115L; private SerializedSingleton(){} private static class SingletonHelper{ private static final SerializedSingleton instance = new SerializedSingleton(); } public static SerializedSingleton getInstance(){ return SingletonHelper.instance; } }
在序列化singleton类的问题时,无论我们怎么设计,当我们反序列化,它都会创建一个类的新实例。让我们来看看它有一个简单的测试程序。
package com.journaldev.singleton; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectInput; import java.io.ObjectInputStream; import java.io.ObjectOutput; import java.io.ObjectOutputStream; public class SingletonSerializedTest { public static void main(String[] args) throws FileNotFoundException, IOException, ClassNotFoundException { SerializedSingleton instanceOne = SerializedSingleton.getInstance(); ObjectOutput out = new ObjectOutputStream(new FileOutputStream( "filename.ser")); out.writeObject(instanceOne); out.close(); //deserailize from file to object ObjectInput in = new ObjectInputStream(new FileInputStream( "filename.ser")); SerializedSingleton instanceTwo = (SerializedSingleton) in.readObject(); in.close(); System.out.println("instanceOne hashCode="+instanceOne.hashCode()); System.out.println("instanceTwo hashCode="+instanceTwo.hashCode()); } }
输出结果:
instanceOne hashCode=2011117821
instanceTwo hashCode=109647522
它会毁掉单例模式,为了避免这一情况发生,我们需要实现一个readResolve()
方法
protected Object readResolve() { return getInstance(); }
在实现该方法后一会发现,上述两个单例hashcode是一样的。
原文链接:http://www.journaldev.com/1377/java-singleton-design-pattern-best-practices-examples