单例模式的关键点
1) 构造方法不对外开放,为private
2) 确保单例类只有一个对象,尤其是多线程模式下
3) 通过静态方法或枚举返回单例对象
4) 确保单例类在反序列化是不会重新创建新的对象
单例模式的实现方式
1) 饿汉式
public class Singleton1 { /* * 饿汉式是在声明的时候就已经初始化Singleton1,确保了对象的唯一性 * * 声明的时候就初始化对象会浪费不必要的资源 * */ private static Singleton1 instance = new Singleton1(); private Singleton1() { } // 通过静态方法或枚举返回单例对象 public Singleton1 getInstance() { return instance; } }
2) 懒汉式
public class Singleton2 { private static Singleton2 instance; private Singleton2() { } /* * getInstance 添加了synchronized 关键字,,也就是说 getInstance 是一个同步方法, * 这就是懒汉式在多线程中保持唯一性的方式 * * 懒汉式存在的问题是,即是instance已经被创建,每次调用getInstance方法还是会同步,这样就会浪费一些不必要的资源 * */ public static synchronized Singleton2 getInstance() { if (instance == null) { instance = new Singleton2(); } return instance; } }
3) Double Check Lock(DCL模式)
private static Singleton3 instance; private Singleton3() { } /** * getInstance 进行了两次判空,第一次判空是为了不必要的同步,第二次判空为了在instance 为 null 的情况下创建实例 * 既保证了线程安全且单例对象初始化后调用getInstance又不会进行同步锁判断 * * 优点:资源利用率高,效率高 * 缺点:第一次加载稍慢,由于java处理器允许乱序执行,偶尔会失败 * * @return */ public static Singleton3 getInstance() { if (instance == null) { synchronized (Singleton3.class) { if (instance == null) { instance = new Singleton3(); } } } return instance; } }
4) 静态内部类实现单例模式
5) public class Singleton4 { /* *当第一次加载Singleton类时并不会初始化SINGLRTON,只有第一次调用getInstance方法的时候才会初始化SINGLETON *第一次调用getInstance 方法的时候虚拟机才会加载SingletonHoder类,这种方式不仅能够保证线程安全,也能够保证对象的唯一, *还延迟了单例的实例化,所有推荐使用这种方式 * */ private Singleton4() { } public Singleton4 getInstance() { return SingletonHolder.SINGLETON; } private static class SingletonHolder { private static final Singleton4 SINGLETON = new Singleton4(); } }
6) 枚举实现单例模式
public enum SingletonEnum { /** *枚举是写法最简单的 * 默认枚举实例的创建时线程安全的,且在任何一种情况下它都是单例,包括反序列化 * */ INSTANCE; }
7) 使用容器实现单例
public class Singleton5 { /** * 将多种类型的单例放到统一的Map将集合中,根据相应的Key获取对应的对象 * * 这种方式是我们可以管理多种类型的单例,可以使用统一接口进行获取操作 * 降低了使用成本,也隐藏了具体实现,降低了耦合度 */ public static MapobjMap = new HashMap (); private Singleton5() { } public static void registerInstance(String key, Object instance) { if (!objMap.containsKey(key)) { objMap.put(key, instance); } } public static Object getInstance(String key) { return objMap.get(key); } }
反序列化时会重新生成对象的问题
已上几种方法里面 枚举任何一种情况都是单例,不存在这个问题,容器模式只是承载单例的容器所以它本身也不存在这个问题,只有其他三种方式种存在这个反序列话的问题,怎么解决呢?加入如下代码
// 防止反序列化获取多个对象的漏洞 // 实现Serializable接口,当从I/O流中读取对象时,readResolve()方法都会被调用到 //用readResolve()中返回的对象直接替换在反序列化过程中创建的对象 private Singleton4 readResolve() throws ObjectStreamException { return SingletonHolder.SINGLETON; }