前言 在上一篇文章中,讲解了关于设计模式的一些背景和基础知识。那么从这一篇开始,我们就正式开始设计模式专题的学习。 什么是单例模式顾名思义,单例单例,单个实例,使用该设计模式保证一个类仅有一个实例。 使用场景在项目中全局只需要一个对象实例就可以考虑使用单例模式,例如Spring框架中的Bean、读取配置文件的类等等。 单例模式实现方式单例模式实现的方式很多,总结为以下5种方式: 饿汉式 懒汉式 双重检查锁 静态内部类 枚举 有的小伙伴可能会问为什么会有这么多的实现方式?当然是因为已有的实现方式有缺陷才会诞生新的方式。 饿汉式public class HungrySingleton { /** * 提前创建对象 */ private static final HungrySingleton HUNGRY_SINGLETON = new HungrySingleton(); /** * 构造函数私有化 */ private HungrySingleton() { } /** * 对外提供获取单例对象的静态方法 * @return HungrySingleton */ public static HungrySingleton getInstance() { return HUNGRY_SINGLETON; } }12345678910111213141516171819202122复制代码类型:[java] 从代码来看,之所以叫饿汉式是因为太饿了,不想等待食物生产时间,所以提前创建了对象,有一种迫不及待的心情。 优点:实现简单,没有线程安全问题 缺点:如果该对象不使用就相当于在浪费内存 懒汉式懒汉式较比饿汉式来说,延时加载对象,在第一次调用获取对象的方法时才正式创建对象。 第一种: public class LazySingleton { /** * 定义单例对象变量 */ private static LazySingleton lazySingleton; /** * 构造函数私有化 */ private LazySingleton() { } /** * 第一种方式 * 对外提供获取单例对象的静态方法 * 缺点:线程不安全 * @return LazySingleton */ public static LazySingleton getInstanceOne() { if (lazySingleton == null) { lazySingleton = new LazySingleton(); } return lazySingleton; } }123456789101112131415161718192021222324252627复制代码类型:[java] 缺点:在多线程下是不安全的,假设A线程执行完lazySingleton == null还没执行实例化语句,此时对B线程来说lazySingleton还是为null,会跟A线程同时执行实例化。尽管对于AB线程执行完方法后才访问的其它线程来说最终拿到的对象是同一个,但是多实例化了一次对象,给程序带来垃圾对象,如果初始化完成之前访问的线程越多,那造成的垃圾对象也就越多。 第二种: public class LazySingleton { /** * 定义单例对象变量 */ private static LazySingleton lazySingleton; /** * 构造函数私有化 */ private LazySingleton() { } /** * 第二种方式 * 通过 synchronized 保证线程安全 * 缺点:锁方法导致并发量降低 * @return LazySingleton */ public static synchronized LazySingleton getInstanceTwo() { if (lazySingleton == null) { lazySingleton = new LazySingleton(); } return lazySingleton; } }123456789101112131415161718192021222324252627复制代码类型:[java] 优点:对比第一种,解决了线程安全问题 缺点:由于加了synchronized锁,会降低并发量 双重检查锁(DCL-> Double-Checked-Lock)双重检查锁方式我们从名称上能大致猜的出来,会有两次判断,那么具体怎么实现我们来看下面的代码。 public class LazySingleton { /** * 定义单例对象变量 * volatile 解决 getInstance() 非原子性操作问题 */ private static volatile LazySingleton lazySingleton; /** * 构造函数私有化 */ private LazySingleton() { } /** * 优点:降低 synchronized 锁的范围 提高并发量 * 缺点:实例化对象非原子性操作 * 解决方案:使用 volatile 关键字修饰引用 * @return LazySingleton */ public static LazySingleton getInstance() { // 第一重检查 if (lazySingleton == null) { synchronized (LazySingleton.class) { // 第二重检查 if (lazySingleton == null) { // 非原子性操作 可能导致对象创建不完整 // 解决方案:给 lazySingleton 加上 volatile lazySingleton = new LazySingleton(); } } } return lazySingleton; } }123456789101112131415161718192021222324252627282930313233343536复制代码类型:[java] 优点:较比懒汉式的第二种降低 synchronized 锁的范围,提高并发量。 缺点:因为实例化对象语句在被解析成字节码的时候是多个指令,非原子性操作,可能会出现对象创建不完整的情况,这个涉及JVM的知识,这里就不细讲了。解决方案是给 lazySingleton 变量加上volatile关键字。 静态内部类静态内部类的方式,是通过在单例Class中再创建个静态的Class,通过在静态类中进行实例化从而达到线程安全的懒加载效果。静态内部类的用法也许很多老铁们用的不多,实际工作中也不怎么解除,但是这种编码方式在开源框架中是很常见的。 public class InnerSingleton { /** * 构造函数私有化 */ private InnerSingleton() { } /** * 在静态内部类中初始化对象 */ private static class SingletonHolder { private static final InnerSingleton INNER_SINGLETON = new InnerSingleton(); } /** * 第一次调用该方法会触发内部类中的初始化 * JVM保证静态类在初始化过程中只初始化一次 * @return InnerSingleton */ public static InnerSingleton getInstance() { return SingletonHolder.INNER_SINGLETON; } }1234567891011121314151617181920212223242526复制代码类型:[java] 优点:线程安全的懒加载方式,同时不会降低并发量 枚举上面已经讲了好几种实现单例的方式以及优缺点,并且静态内部类看起来也近乎完美,为什么还需要讲枚举呢。其实可以使用反射和序列化的方式打破上述方案的安全性,下面我们就来看下具体如何实现。 import java.io.*;import java.lang.reflect.Constructor;/** * 需实现 Serializable 接口 */public class InnerSingleton implements Serializable { /** * 构造函数私有化 */ private InnerSingleton() { } /** * 在静态内部类中初始化对象 */ private static class SingletonHolder { private static final InnerSingleton INNER_SINGLETON = new InnerSingleton(); } /** * 第一次调用该方法会触发内部类中的初始化 * JVM保证静态类在初始化过程中只初始化一次 * * @return InnerSingleton */ public static InnerSingleton getInstance() { return SingletonHolder.INNER_SINGLETON; } /** * 序列化解决方案 * @return Object */// private Object readResolve() {// return SingletonHolder.INNER_SINGLETON;// } public static void main(String[] args) throws Exception { InnerSingleton innerSingleton = InnerSingleton.getInstance(); // 反射方式 Constructor<InnerSingleton> constructor = InnerSingleton.class.getDeclaredConstructor(); constructor.setAccessible(true); InnerSingleton newInstance = constructor.newInstance(); // 判断是否是同一个对象 System.out.println(innerSingleton == newInstance); // 序列化方式 // 把对象写到文件中 ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("single.txt")); oos.writeObject(innerSingleton); oos.close(); // 从原文件中读取对象 File file = new File("single.txt"); ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file)); InnerSingleton ioNewInstance = (InnerSingleton) ois.readObject(); ois.close(); // 判断是否是同一个对象 System.out.println(innerSingleton == ioNewInstance); } }123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566复制代码类型:[java] 运行结果: falsefalse12复制代码类型:[java] 序列化的解决方案将 readResolve 方法注释打开即可,原理在本篇中不做讲解,后面有机会可以单独写一篇。最后我们来实现下枚举,如下所示。 import java.lang.reflect.Constructor;public enum EnumSingleton { /** * EnumSingleton的一个实例,天然单例 */ INSTANCE; public static void main(String[] args) throws Exception { EnumSingleton instance1 = EnumSingleton.INSTANCE; EnumSingleton instance2 = EnumSingleton.INSTANCE; System.out.println(instance1 == instance2); // 反射实例化对象 // 获取自身构造器 Constructor<EnumSingleton> constructor1 = EnumSingleton.class.getDeclaredConstructor(); constructor1.setAccessible(true); EnumSingleton instance3 = constructor1.newInstance(); // 获取父类构造器 Constructor<EnumSingleton> constructor2 = EnumSingleton.class.getDeclaredConstructor(String.class, int.class); constructor2.setAccessible(true); EnumSingleton instance4 = constructor2.newInstance(); System.out.println(instance1 == instance3); System.out.println(instance2 == instance3); System.out.println(instance3 == instance4); } }123456789101112131415161718192021222324252627复制代码类型:[java] 运行结果: true// 自身构造器运行结果Exception in thread "main" java.lang.NoSuchMethodException: com.javafamily.EnumSingleton.<init>() at java.base/java.lang.Class.getConstructor0(Class.java:3349) at java.base/java.lang.Class.getDeclaredConstructor(Class.java:2553) at com.javafamily.EnumSingleton.main(EnumSingleton.java:17)// 父类构造器运行结果Exception in thread "main" java.lang.IllegalArgumentException: Cannot reflectively create enum objects at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:484) at com.javafamily.EnumSingleton.main(EnumSingleton.java:24)12345678910复制代码类型:[java] 优点:实现简单,线程安全,自动支持序列化机制,通过反射生成新的实例会抛出异常,第一个异常通过getDeclaredConstructors()获取所有构造方法,会发现并没有无参构造方法,只有参数为(String.class,int.class)构造方法,而该方法恰好是Enum类。而第二个异常在 newInstance 方法中已经说明原因,如果该类被Enum修饰,则无法发射抛出异常。 @CallerSensitive@ForceInline // to ensure Reflection.getCallerClass optimizationpublic T newInstance(Object ... initargs) throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException{ if (!override) { Class<?> caller = Reflection.getCallerClass(); checkAccess(caller, clazz, clazz, 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; }1234567891011121314151617181920复制代码类型:[java] 总结:至此,单例的实现方式都讲完了,也许在实际开发中除了枚举以外,其它实现单例的方式都比较常见,但枚举是《Effective Java》 作者提倡的方式,相信该方式在未来也将慢慢成为主流。 |
|