单例(Singleton)是指只实例化一次的类。
一、如何实现单例
单例类常用的实现方式有以下几种
1、饿汉式:类载入时创建单例实例
1.1、公开的静态常量单例对象
/** * * * <p> * * * <h6>当类StaticFinalSingleton类被实例化时,有且仅有一个StaticFinalSingleton实例存在 * * (除非客户端通过类反射机制调用该私有的构造方法,否则外部无法改变单例)</h6> * * @Copyright 2011 * * </p> */ public class StaticFinalSingleton { /** * 公有静态常量单例对象 */ public static final StaticFinalSingleton INSTANCE = new StaticFinalSingleton(); /** * 私有构造方法 */ private StaticFinalSingleton() { } }
1.2、静态工厂方法获取私有静态常量单例对象
/** * * * <p> * * * <h6>当类StaticFinalSingleton类被实例化时,有且仅有一个StaticFinalSingleton实例存在 * * (除非客户端通过类反射机制调用该私有的构造方法,否则外部无法改变单例)</h6> * * @Copyright 2011 * * </p> */ public class StaticFinalSingleton { /** * 私有静态常量单例对象 */ private static final StaticFinalSingleton INSTANCE = new StaticFinalSingleton(); /** * 私有构造方法 */ private StaticFinalSingleton() { } /** * 静态工厂方法获取StaticFinalSingleton型的私有静态常量单例对象 */ public static StaticFinalSingleton getInstanceFactory() { return INSTANCE; } }
2、懒汉式:延迟加载创建单例实例
2.1、基于静态同步锁的lazy initialization单例类
此种方法不常用,因为在实际中会大大影响并发度,性能较差
/** * <p> * <h6>基于静态同步锁的lazy initialization单例类:<br> * 此种方法不常用,因为在实际中会大大影响并发度,并且可能不够安全</h6> * * @Copyright 2011 * </p> */ public class SynLockSingleton { /** * 私有静态常量单例对象 */ private static SynLockSingleton INSTANCE = null; /** * 私有构造方法 */ private SynLockSingleton() { } /** * 返回单例实例 */ public static synchronized SynLockSingleton getInstanceFactory() { if (INSTANCE == null) { INSTANCE = new SynLockSingleton(); } return INSTANCE; } }
2.2、基于双检查锁机制的lazy initialization单例类
双检查锁只有在 jdk 1.5 及以上版本才能达到单例的效果,因此不常用
/** * <p> * <h6>基于双检查锁机制的lazy initialization单例类:<br> * 双检查锁只有在 jdk 1.5 及以上版本才能达到单例的效果,因此不常用</h6> * * @Copyright 2011 * </p> */ public class DCLSingleton { private static DCLSingleton INSTANCE = null; /** * 私有构造方法 */ private DCLSingleton() { } /** * 返回单例实例 */ public static DCLSingleton getInstance() { if (INSTANCE == null) { synchronized (DCLSingleton.class) { if (INSTANCE == null) { INSTANCE = new DCLSingleton(); } } } return INSTANCE; } }
2.3、基于静态内部类的lazy initialization单例类
/** * <p> * <h6>基于静态内部类的lazy initialization单例类<br> * </h6> * * @Copyright 2011 * </p> */ public class StaticInnerSingleton { /** * 私有构造方法 */ private StaticInnerSingleton() { } private static final class InnerBuildSingleton { private static final StaticInnerSingleton INSTANCE = new StaticInnerSingleton(); } /** * 返回单例实例 */ public static StaticInnerSingleton getInstanceFactory() { return InnerBuildSingleton.INSTANCE; } }
3、基于枚举实现单例
使用枚举实现单例:如果枚举中只有一个元素,那么就可以使用单例设计模式,java1.5以上提供的功能。
此种方式应该是最有效和最安全的单例实现方式,由JVM控制单例的初始化和管理,并且避免了通过反射再次生成单例的可能
/** * <p> * <h6>使用枚举实现单例:如果枚举中只有一个元素,那么就可以使用单例设计模式</h6> * * @Copyright 2011 * </p> */ public enum EnumSingleton { /** * 枚举类型 INSTANCE 就是一个单例,引用方式为 EnumSingleton.INSTANCE */ INSTANCE; // 后面是单例中的属性及方法,如 void doSomething() { } private EnumSingleton() { } public static void main(String[] args) { // 引用单例并使用其方法 EnumSingleton.INSTANCE.doSomething(); } }
二、Sington类的其他注意事项
1、通过反射生成Sington类的额外实例
在不是通过枚举实现单例时,均面临一个问题,如果通过java的反射机制,是可以生成此单例的额外实例的
1、1举例
import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; public class StaticFinalSingleton { /** * 公有静态常量单例对象 */ public static final StaticFinalSingleton INSTANCE = new StaticFinalSingleton(); /** * 私有构造方法 */ private StaticFinalSingleton() { } /** * 返回据类名通过反射得到的实例 * * @param String * className * @return Object */ @SuppressWarnings("unchecked") public static Object getReflectInstance(String className) throws ClassNotFoundException, IllegalArgumentException, InstantiationException, IllegalAccessException, InvocationTargetException { Class c1 = Class.forName(className); Constructor[] cons = c1.getDeclaredConstructors(); Constructor cc1 = cons[0]; cc1.setAccessible(true); return cc1.newInstance(null); } public static void main(String[] args) { try { // 首先获取单例实例 StaticFinalSingleton s1 = StaticFinalSingleton.INSTANCE; // 通过反射,生成此单例类新的实例 StaticFinalSingleton s2 = (StaticFinalSingleton) getReflectInstance("StaticFinalSingleton"); System.out.println("object instance --s1=" + s1 + ",s2=" + s2); System.out.println("object instance (s1==s2)=" + (s1 == s2)); } catch (Exception e) { e.printStackTrace(); } } }
输出如下
object instance --s1=StaticFinalSingleton@de6ced,s2=StaticFinalSingleton@c17164
object instance (s1==s2)=false
1.2 解决方法
可能的一个解决方法,在私有的构造函数中做检查,判断是否已经实例化了单例类,如果重复实例化,则抛RuntimeException异常
import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; public class StaticFinalSingleton { private static boolean isFirstFlag = false; /** * 公有静态常量单例对象 */ public static final StaticFinalSingleton INSTANCE = new StaticFinalSingleton(); /** * 私有构造方法 */ private StaticFinalSingleton() { if (isFirstFlag) { throw new java.lang.RuntimeException( "can not initialization class=StaticFinalSingleton"); } else { isFirstFlag = true; // do initialization work } } /** * 返回据类名通过反射得到的实例 * * @param String * className * @return Object */ @SuppressWarnings("unchecked") public static Object getReflectInstance(String className) throws ClassNotFoundException, IllegalArgumentException, InstantiationException, IllegalAccessException, InvocationTargetException { Class c1 = Class.forName(className); Constructor[] cons = c1.getDeclaredConstructors(); Constructor cc1 = cons[0]; cc1.setAccessible(true); return cc1.newInstance(null); } public static void main(String[] args) { try { // 首先获取单例实例 StaticFinalSingleton s1 = StaticFinalSingleton.INSTANCE; // 通过反射,生成此单例类新的实例 StaticFinalSingleton s2 = (StaticFinalSingleton) getReflectInstance("StaticFinalSingleton"); System.out.println("object instance --s1=" + s1 + ",s2=" + s2); System.out.println("object instance (s1==s2)=" + (s1 == s2)); } catch (Exception e) { e.printStackTrace(); } } }
此时,通过反射无法实现创建额外的单例
输出如下所示:
java.lang.reflect.InvocationTargetException
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:39)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:27)
at java.lang.reflect.Constructor.newInstance(Constructor.java:513)
at StaticFinalSingleton.getReflectInstance(StaticFinalSingleton.java:47)
at StaticFinalSingleton.main(StaticFinalSingleton.java:55)
Caused by: java.lang.RuntimeException: can not initialization class=StaticFinalSingleton
at StaticFinalSingleton.<init>(StaticFinalSingleton.java:23)
... 6 more
2、多个类加载实现单例类的加载
在许多情况下,使用多个类载入器是很普遍的--包括servlet容器--所以不管你在实现你的单例类时是多么 小心你都最终可以得到多个单例类的实例。如果你想要确保你的单例类只被同一个的类载入器装入,就必须自己指定这个类载入器
public class StaticFinalSingleton { public static final StaticFinalSingleton INSTANCE = new StaticFinalSingleton(); /** * 私有构造方法 */ private StaticFinalSingleton() { } /** * 重写getClass方法以应对多个类载入器的情况 * * @param String * className * @return Class */ @SuppressWarnings("unused") private static Class getClass(String classname) throws ClassNotFoundException { ClassLoader classLoader = Thread.currentThread() .getContextClassLoader(); if (classLoader == null) classLoader = StaticFinalSingleton.class.getClassLoader(); return (classLoader.loadClass(classname)); } }
方法会尝试把当前的线程与那个类载入器相关联;如果classloader为null,这个方法会使用与装入单例类基类的那个类载入器
3、单例类的序列化
为了使Singleton类变成可序列化的(serializable),仅仅实现Serializable接口是不够的。为了维护 Singleton的单例性,你必须给Singleton类提供一个readResolve方法,否则的话,一个序列化的实例,每次反序列化的时候都会产生一个新的实例。Singleton 也不会例外。
3.1举例
import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; public class StaticFinalSingleton implements java.io.Serializable { private static final long serialVersionUID = 2474672098172796959L; public static final StaticFinalSingleton INSTANCE = new StaticFinalSingleton(); /** * 私有构造方法 */ private StaticFinalSingleton() { } public static void main(String[] args) { ObjectOutputStream objectOutputStream = null; ObjectInputStream objectInputStream = null; try { // 序列化 objectOutputStream = new ObjectOutputStream(new FileOutputStream( "C:\\StaticFinalSingleton.obj")); StaticFinalSingleton singleton1 = StaticFinalSingleton.INSTANCE; objectOutputStream.writeObject(singleton1); objectOutputStream.close(); // 反序列化 objectInputStream = new ObjectInputStream(new FileInputStream( "C:\\StaticFinalSingleton.obj")); StaticFinalSingleton singleton2 = (StaticFinalSingleton) objectInputStream .readObject(); objectInputStream.close(); // 比较是否原来的实例 System.out.println("object instance (singleton1==singleton2)=" + (singleton1 == singleton2)); } catch (Exception e) { } finally { if (objectOutputStream != null) { try { objectOutputStream.close(); } catch (IOException e) { } } if (objectInputStream != null) { try { objectInputStream.close(); } catch (IOException e) { } } } } }
输出如下
object instance (singleton1==singleton2)=false
3.2解决方法
Serializable接口确实有这样两个特殊的方法描述:
Object writeReplace() throws ObjectStreamException;
此 writeReplace 方法将由序列化调用,前提是如果此方法存在,而且它可以通过被序列化对象的类中定义的一 个方法访问。因此,该方法可以拥有私有 (private)、受保护的 (protected) 和包私有 (package-private) 访 问。子类对此方法的访问遵循 java 访问规则。
在从流中读取类的一个实例时需要指定替代的类应使用的准确签名来实现此特殊方法:
Object readResolve() throws ObjectStreamException;
此 readResolve 方法遵循与 writeReplace 相同的调用规则和访问规则。
上述两个方法的只要出现,就会履盖以下两个方法(这两个方法本质的意义就是用来替换序列与反序列的对 象),虽然会执行它们,但最后得到的结果却是writeReplace、readResolve两个方法写入或读出的对象:
private void writeObject(java.io.ObjectOutputStream out) throws IOException
private void readObject(java.io.ObjectInputStream in)throws IOException, ClassNotFoundException;
另外,writeObject与readObject需成对实现,而writeReplace与readResolve则不需要成对出现,一般单独使 用。如果同时出现这四个方法,最后写入与读出的结果以writeReplace和readResolve方法的结果为准。
因此我们只要为Singleton类增加readResolve()方法:
import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; public class StaticFinalSingleton implements java.io.Serializable { private static final long serialVersionUID = 2474672098172796959L; public static final StaticFinalSingleton INSTANCE = new StaticFinalSingleton(); /** * 私有构造方法 */ private StaticFinalSingleton() { } /** * 反序列化时实际上用INSTANCE代替读取的对象,达到唯一一个实例 * * @param String * className * @return Class */ private Object readResolve() { return INSTANCE; } public static void main(String[] args) { ObjectOutputStream objectOutputStream = null; ObjectInputStream objectInputStream = null; try { // 序列化 objectOutputStream = new ObjectOutputStream(new FileOutputStream( "C:\\StaticFinalSingleton.obj")); StaticFinalSingleton singleton1 = StaticFinalSingleton.INSTANCE; objectOutputStream.writeObject(singleton1); objectOutputStream.close(); // 反序列化 objectInputStream = new ObjectInputStream(new FileInputStream( "C:\\StaticFinalSingleton.obj")); StaticFinalSingleton singleton2 = (StaticFinalSingleton) objectInputStream .readObject(); objectInputStream.close(); // 比较是否原来的实例 System.out.println("object instance (singleton1==singleton2)=" + (singleton1 == singleton2)); } catch (Exception e) { } finally { if (objectOutputStream != null) { try { objectOutputStream.close(); } catch (IOException e) { } } if (objectInputStream != null) { try { objectInputStream.close(); } catch (IOException e) { } } } } }
输出如下所示:
object instance (singleton1==singleton2)=true