单例模式

# 单例模式 ## 概念 单例模式:指一个类在任何情况下都绝对只有一个实例,并提供一个全局访问点(getInstance方法)。 大概实现就是隐藏其构造方法,单例模式属于创建型模式。 一些实际的应用场景比如,DBpool, ServletContext,ServletConfig等 ## 单例模式写法 ### 饿汉式单例 在单例类首次加载时创建实例; ```java public class HungrySingleton { private static final HungrySingleton hungrySingleton = new HungrySingleton(); private HungrySingleton(){} public static HungrySingleton getInstance(){ return hungrySingleton; } } ``` > 优点 1、执行效率高,没有加任何锁 > 缺点 1、类加载的时候就初始化,在某些情况下,可能会造成内存浪费;如果出现类的数量很多的时候,会初始化很多类,占用大量内存; > 局限性 Spring就不能使用,Spring启动的时候,会有大量的类加载。 **饿汉式的第二种写法** ```java public class HungryStaticSingleton { private static final HungryStaticSingleton hungrySingleton ; static { hungrySingleton = new HungryStaticSingleton(); } private HungryStaticSingleton(){} public static HungryStaticSingleton getInstance(){ return hungrySingleton; } } ``` 区别仅仅实在与类加载的顺序不同。 ### 懒汉式单例 被外部类调用时才创建实例; ```java public class LazySingleton { private static LazySingleton instance = null; private LazySingleton() { } public static LazySingleton getInstance() { if (instance == null) { instance = new LazySingleton(); } return instance; } } ``` > 优点 1、节省了内存 > 缺点 1、不能保证真实单例,线程不安全 **线程不安全的原因有两种** - 后面的线程覆盖掉前面线程创建的实例 - 同时进入判断条件,按顺序返回,没有覆盖的时候,就返回实例 解决方法: ``` public static synchorized LazySingleton getInstance() { if (instance == null) { instance = new LazySingleton(); } return instance; } ``` 但是getInstance方法添加上锁之后,性能下降,如果有很多请求访问,除了获得锁的线程之外,其他线程都要等待。 **如何优化?** ```java public class LazyDclSingleton { private static LazyDclSingleton instance = null; private LazyDclSingleton() { } // 后面的线程覆盖掉前面线程创建的实例 public static LazyDclSingleton getInstance() { if (instance == null) { synchronized (LazyDclSingleton.class) { instance = new LazyDclSingleton(); } } return instance; } } ``` #### 双重检查锁 ```java public class LazyDclSingleton { private static volatile LazyDclSingleton instance = null; private LazyDclSingleton() { } public static LazyDclSingleton getInstance() { // 检查是否要阻塞 if (instance == null) { synchronized (LazyDclSingleton.class) { // 检查是否要创建实例 if (instance == null) { instance = new LazyDclSingleton(); } } } return instance; } } ``` > 局限性 会出现指令重排序的问题,有可能返回一个不完整的实例 解决方案:private static volatile LazyDclSingleton instance = null;(volatile禁止指令重排序) ### 双重检查锁的有缺点 > 优点 1、性能高,能保证线程安全 > 缺点 1、代码可读性查,不够美观,代码不够优雅 ### 静态内部类写法 ```java /** * 静态内部类 * 静态内部类在使用时才进行构建 * classPath: ../LazyInnerClassSingleton.class * ../LazyInnerClassSingleton$LazyHolder.class * * 优点:写法优雅,利用了java语言语法,性能也高,避免内存的浪费 * 缺点:能够被反射破坏 */ public class LazyInnerClassSingleton { private LazyInnerClassSingleton(){} public static LazyInnerClassSingleton getInstance(){ return LazyHolder.instance; } private static class LazyHolder{ private static final LazyInnerClassSingleton instance = new LazyInnerClassSingleton(); } } ``` > 优点 1、写法优雅,利用了java语言语法 2、性能也高 3、避免内存的浪费 > 缺点 1、能够被反射破坏单例 ```java /** * 反射破坏单例模式 */ public class ReflectTest { public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { Class<?> clazz = LazyInnerClassSingleton.class; Constructor<?> declaredConstructor = clazz.getDeclaredConstructor(null); declaredConstructor.setAccessible(true); Object instance = declaredConstructor.newInstance(); System.err.println(instance); } } // 打印结果 com.ibli.javaBase.pattern.singleton.LazyInnerClassSingleton@38af3868 ``` 解决办法: 在构造器中添加一个判断,如果实例已经创建,则直接抛出异常终止创建; ``` private LazyInnerClassSingleton(){ if (LazyHolder.instance != null){ throw new IllegalArgumentException(); } } ``` ### 注册式单例 > 将每一个实例都缓存到一个容器中,使用唯一标志获取实例 ### 枚举写法 ```java public enum EnumSingleton { INSTANCE; private Object object; public Object getObject() { return object; } public void setObject(Object object) { this.object = object; } public static EnumSingleton getInstance(){ return INSTANCE; } } ``` 使用与测试 ```java public class EnumSingletonTest { public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { EnumSingleton enumSingleton = EnumSingleton.getInstance(); enumSingleton.setObject(new Object()); // 尝试使用反射破坏 Class<?> clazz = EnumSingleton.class; Constructor<?> declaredConstructor = clazz.getDeclaredConstructor(String.class,int.class); System.err.println(declaredConstructor); declaredConstructor.setAccessible(true); Object object = declaredConstructor.newInstance(); System.err.println(object); } } ``` 测试结果: ```text private com.ibli.javaBase.pattern.singleton.EnumSingleton(java.lang.String,int) Exception in thread "main" java.lang.IllegalArgumentException: Cannot reflectively create enum objects at java.lang.reflect.Constructor.newInstance(Constructor.java:417) at com.ibli.javaBase.pattern.singleton.EnumSingletonTest.main(EnumSingletonTest.java:17) ``` 原因在JDK底层源码中已经做了限制 ```text @CallerSensitive public T newInstance(Object ... initargs) throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException { if (!override) { if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) { Class<?> caller = Reflection.getCallerClass(); checkAccess(caller, clazz, null, modifiers); } } // 不能创建枚举类型 if ((clazz.getModifiers() & Modifier.ENUM) != 0) throw new IllegalArgumentException("Cannot reflectively create enum objects"); ConstructorAccessor ca = constructorAccessor; // read volatile if (ca == null) { ca = acquireConstructorAccessor(); } @SuppressWarnings("unchecked") T inst = (T) ca.newInstance(initargs); return inst; } ``` ### 枚举写法优缺点 > 优点 1、写法优雅,使用方便 > 缺点 2、和饿汉式一样,在某些情况下会造成大量内存浪费 ### 容器式单例写法 ```java public class ContainerSingleton { private ContainerSingleton() { } private static Map<String, Object> ioc = new ConcurrentHashMap<>(); public static Object getInstance(String className) { Object instance = null; if (!ioc.containsKey(className)) { try { instance = Class.forName(className).newInstance(); ioc.put(className, instance); } catch (Exception e) { e.printStackTrace(); } return instance; } else { return ioc.get(className); } } } ``` 测试类: ```java public class ContainerSingletonTest { public static void main(String[] args) { Object o1 = ContainerSingleton.getInstance("com.ibli.javaBase.pattern.singleton.Pojo"); Object o2 = ContainerSingleton.getInstance("com.ibli.javaBase.pattern.singleton.Pojo"); System.err.println(o1 == o2); // true } } ``` 容器式单例写法适合创建大量单例实例的场景,类似与Spring的IOC容器。 当然上面的写法也会存在一个线程安全问题; // TODO ### 序列化破坏单例模式 ```java /** * 序列化:把内存中对象的状态转换为字节码的形式,然后在把字节码以IO输出流写到磁盘上 * 反序列化: 将持久化的字节码内容,通过IO流的方式读取到内存中,然后在转换成Java对象 */ public class SerializableSingleton implements Serializable { private static final SerializableSingleton serializableSingleton = new SerializableSingleton(); private SerializableSingleton() { } public static SerializableSingleton getInstance() { return serializableSingleton; } } ``` 测试类: ```java public class SerializableSingletonTest { public static void main(String[] args) { SerializableSingleton s1; SerializableSingleton s2 = SerializableSingleton.getInstance(); FileOutputStream fos; try { fos = new FileOutputStream("SerializableSingleton.obj"); ObjectOutputStream oos = new ObjectOutputStream(fos); oos.writeObject(s2); oos.flush(); oos.close(); FileInputStream fis = new FileInputStream("SerializableSingleton.obj"); ObjectInputStream ois = new ObjectInputStream(fis); s1 = (SerializableSingleton) ois.readObject(); ois.close(); System.out.println(s1); System.out.println(s2); System.out.println(s1 == s2); } catch (Exception e) { e.printStackTrace(); } } } ``` 结果: ```text com.ibli.javaBase.pattern.singleton.SerializableSingleton@20ad9418 com.ibli.javaBase.pattern.singleton.SerializableSingleton@681a9515 false ``` 解决方法:在SerializableSingleton中添加一个方法 ```text // 桥接模式 private Object readResolve() { return serializableSingleton; } ``` 结果: ```text com.ibli.javaBase.pattern.singleton.SerializableSingleton@681a9515 com.ibli.javaBase.pattern.singleton.SerializableSingleton@681a9515 true ``` 原因:ois.readObject();方法底层有对readResolve方法的判断,如果不存在这个方法,会利用反射生成一个新的实例; ### ThreadLocal单例 下面介绍一种比较少见的一种单例模式 ```java public class ThreadLocalSingleton { private static final ThreadLocal<ThreadLocalSingleton> threadLocalSingleton = new ThreadLocal<ThreadLocalSingleton>() { @Override protected ThreadLocalSingleton initialValue() { return new ThreadLocalSingleton(); } }; private ThreadLocalSingleton(){} public static ThreadLocalSingleton getInstance(){ return threadLocalSingleton.get(); } } ``` ```java public class ThreadLocalExector implements Runnable{ @Override public void run() { System.err.println(ThreadLocalSingleton.getInstance()); } } ``` 测试: ```java public class ThreadLocalSingletonTest { public static void main(String[] args) { System.out.println(ThreadLocalSingleton.getInstance()); System.out.println(ThreadLocalSingleton.getInstance()); Thread thread1 = new Thread(new ThreadLocalExector()); Thread thread2 = new Thread(new ThreadLocalExector()); thread1.start(); thread2.start();; } } ``` 结果: ```text com.ibli.javaBase.pattern.singleton.ThreadLocalSingleton@38af3868 1 com.ibli.javaBase.pattern.singleton.ThreadLocalSingleton@38af3868 2 com.ibli.javaBase.pattern.singleton.ThreadLocalSingleton@10c69a60 3 com.ibli.javaBase.pattern.singleton.ThreadLocalSingleton@2f1eeb2f 4 ``` 以上3和4的结果虽然不一样,但是其实也是实现了【单例】的效果。 > <p align="middle"> 山脚太拥挤 我们更高处见。 </p>