单例模式
定义
单例模式(SingletonPattern)是指确保一个类在任何情况下都绝对只有一个实例,并提供一个全局访问点。单例模式是创建型模式。
饿汉式
-
优点:没有加任何的锁、执行效率比较高,在用户体验上来说,比懒汉式更好。
缺点:类加载的时候就初始化,不管用与不用都占着空间,浪费了内存,有可能占着茅坑不拉屎。
public class Singleton01 { private Singleton01() { } private static final Singleton01 instance = new Singleton01(); public static Singleton01 getInstance() { return instance; } }
懒汉式
dcl(双重检查)
public class Singleton02 { private Singleton02() { } // 禁止指令重排序 private volatile static Singleton02 instance = null; // 双重检查 private static Singleton02 getInstance() { if(instance == null){ synchronized (Singleton01.class){ if(instance == null){ instance = new Singleton02(); } } } return instance; } }
静态内部类
-
静态内部类在外部类加载的时候不会被加载,只有外部类中用到内部类时候才会被加载
-
由于静态内部类没有使用任何锁机制,所以性能优于双重检查实现方式。
public class Singleton03 { private Singleton03() { } public static Singleton03 getInstance(){ return Singleton03Holder.lazy; } // 静态内部类 private static class Singleton03Holder { public final static Singleton03 lazy = new Singleton03(); } }
枚举单例
-
jvm判断了枚举无法反射获取对象,无法序列化,防止了反射破坏单例和序列化破坏单例
public enum SingletonEnum { INSTANCE; private Object data = new Object(); public Object getData(){ return data; } public static SingletonEnum getInstance(){ return INSTANCE; } }
ThreadLocal在线程内实现单例
-
使用ThreadLocal动态切换数据源
反射破坏单例以及解决方案
-
以上三种单例的写法已经很完善了,但是挡不住反射创建对象,调用者反射破坏单例
public class ReflectBlockSingleton { /** * 反射破坏单例 * @param args */ public static void main(String[] args) { Class<Singleton01> clazz = Singleton01.class; try { Constructor<Singleton01> constructor = clazz.getDeclaredConstructor(null); constructor.setAccessible(true);//强吻 Singleton01 s1 = constructor.newInstance(); Singleton01 s2 = Singleton01.getInstance(); System.out.println(s1 == s2); // false } catch (Exception e) { e.printStackTrace(); } } }
- 解决方案:在私有构造方法中增加判断
private Singleton01() { if(instance != null){ throw new RuntimeException("实例已经创建!"); } }
序列化破坏单例以及解决方案
-
将单例实例创建出来后,序列化到一个文件中,再读出来反序列化为对象
public class SeriableBlockSingletonTest { public static void main(String[] args) { Singleton01 s1 = null; Singleton01 s2 = Singleton01.getInstance(); FileOutputStream fos = null; try { fos = new FileOutputStream("SeriableSingleton.obj"); ObjectOutputStream oos = new ObjectOutputStream(fos); oos.writeObject(s2); oos.flush(); oos.close(); FileInputStream fis = new FileInputStream("SeriableSingleton.obj"); ObjectInputStream ois = new ObjectInputStream(fis); s1 = (Singleton01)ois.readObject(); ois.close(); System.out.println(s1); System.out.println(s2); System.out.println(s1 == s2); } catch (Exception e) { e.printStackTrace(); } } } 解决方案 增加readResolve方法,还是创建了两次对象,只不过在jvm层面被覆盖了。反序列化出来的对象会被GC回收 public class Singleton01 implements Serializable { private Singleton01() { if(instance != null){ throw new RuntimeException("实例已经创建!"); } } private static final Singleton01 instance = new Singleton01(); public static Singleton01 getInstance() { return instance; } private Object readResolve(){ return instance; } }
单例模式总结
-
优点:
内存只有一个实例,减少了内存开销
可以避免对资源的多重占用
设置全局访问点,严格控制访问
-
缺点
没有接口,扩展困难