分享

创建型-单例模式(Singleton Pattern)

 码农9527 2021-11-25

 前言

  在上一篇文章中,讲解了关于设计模式的一些背景和基础知识。那么从这一篇开始,我们就正式开始设计模式专题的学习。

  什么是单例模式

  顾名思义,单例单例,单个实例,使用该设计模式保证一个类仅有一个实例。

  使用场景

  在项目中全局只需要一个对象实例就可以考虑使用单例模式,例如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》 作者提倡的方式,相信该方式在未来也将慢慢成为主流。

    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多