单例模式是一种对象创建模式,用于生产对象的一个具体实例,它可以确保系统中一个类只产生一个实例。该模式能带来两大好处:
1. 对于频繁使用的对象,可以省略创建对象所花费的时间。
2. 由于new的次数减少,因而对系统内存的使用频率也会降低,这将减轻GC压力、缩短gc停顿时间
单例模式的核心,在于通过一个接口返回唯一的对象实例。一个简单的单例实现:
public class Singleton { private Singleton(){ System.out.println("create a Singleton"); } private static Singleton instance = new Singleton(); public static Singleton getInstance(){ return instance; } }首先,单例必须有一个private访问级别的构造函数,只有这样,才能确保单例不会在系统中的其他代码内被实例化;其次,instance成员变量和getInstance()的方法必须是static的
这种实现方式很简单并且还是十分可靠的,它唯一的不足就是无法对instance进行延迟加载,实现延迟的代码为
public class LazySingleton { private LazySingleton(){ System.out.println("LazySingleton is create"); } private static LazySingleton instance = null; public static synchronized LazySingleton getInstance(){ if(instance == null){ instance = new LazySingleton(); } return instance; } }
首先,对于静态变量instance初始值赋予null,确保系统启动时没有额外的负载;
其次,get方法必须是同步的,否则在多线程请扩下,可能会多次创建!
但是由于引入了同步,其性能会和第一种情况差了很多,所以这种降低了系统性能的做法,是有点不可取的,所以我们对其进行改进:
public class StaticSingleton { private StaticSingleton(){ System.out.println("create a StaticSingleton"); } private static class SingletonHolder{ private static StaticSingleton instance = new StaticSingleton(); } public static StaticSingleton getInstance(){ return SingletonHolder.instance; } }
这个实现中,单例模式使用内部类来维护实例,当StaticSingleton被加载时,其内部类不会初始化,故可以在调用getInstance()方法调用,才会加载SingletonHolder.同时,由于实例的建立实在类加载时完成,故天生对多线程友好
通常情况下上面的代码可以确保系统中唯一实例。但是仍有意外情况,比如利用反射,这种情况我们不讨论,我们讨论一种合理的方法:
public class SerSingleton implements Serializable { String name; private SerSingleton(){ System.out.println("create a SerSingleton"); name = "SerSingleton"; } private static SerSingleton instance = new SerSingleton(); public static SerSingleton getInstance(){ return instance; } public static void createString(){ System.out.println("createString in Singleton"); } // private Object readResolve(){ // return instance; // } }
// TODO Auto-generated method stub SerSingleton s1 = null; SerSingleton s = SerSingleton.getInstance(); //先将实例串行化到文件 FileOutputStream fos = new FileOutputStream("s.txt"); ObjectOutputStream oos = new ObjectOutputStream(fos); oos.writeObject(s); oos.flush(); oos.close(); //从文件读出 FileInputStream fis = new FileInputStream("s.txt"); ObjectInputStream ois = new ObjectInputStream(fis); s1= (SerSingleton) ois.readObject(); // Assert.assertEquals(s,s1); System.out.println(s.equals(s1));
如果在第一段代码中去掉readResovle(),那么测试代码(经过串行化和反串行化)后,s和s1指向了不同的实例;加上之后便解决该问题。
即使构造函数是私有的,可序列化工具依然可以通过特殊的途径去创建类的一个新的实例。序列化操作提供了一个很特别的钩子(hook)-类中具有一个私有的被实例化的方法readresolve(),这个方法可以确保类的开发人员在序列化将会返回怎样的object上具有发言权。足够奇怪的,readresolve()并不是静态的,但是在序列化创建实例的时候被引用。