进阶学习之旅-设计模式之单例设计模式

本文详细讲解了单例模式的实现、多线程调试、线程安全策略、反射攻击和序列化破坏的解决方案。涵盖饿汉式、懒汉式(双重检查锁)、内部类、注册式、枚举式和ThreadLocal单例,以及如何防止反射和序列化破坏。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

0.单例设计模式的学习目标

0.1掌握IDEA环境下的多线程调试方式

0.2掌握保证线程安全的单例模式策略

0.3掌握反射暴力攻击单例解决方案及原理分析

0.4掌握序列化破坏单例的原理及解决方案

0.5掌握常见的单例模式的写法

1.单例模式的定义

单例模式(Singleton Pattern) 是指确保一个类在任何情况下都绝对只有一个实例,并提供一个全局访问点。

隐藏其所有的构造方法。

属于创建型模式。

2.单例模式的使用场景

确保任何情况下都绝对只有一个实例。
ServletContext、ServletConfig、ApplicationContext、DBPool

3.单例模式的常见写法

3.1饿汉式单例

在单例类首次加载时就创建实例。

缺点:浪费内存空间

/**
* @PackageName: com.raven.pattern.singleton.hungry
* @ClassName: HungrySingleton
* @Blame: raven
* @Date: 2021-06-19 21:00
* @Description: 饿汉式单例模式
*/
public class HungrySingleton {

   private HungrySingleton() {
   }

   private static final HungrySingleton hungrySingleton = new HungrySingleton();

   public static HungrySingleton getInstance() {
       return hungrySingleton;
   }
}

3.2懒汉式单例

3.2.1 简单的懒汉式单例模式

/**
* @PackageName: com.raven.pattern.singleton.lazy
* @ClassName: LazySimpleSingleton
* @Blame: raven
* @Date: 2021-06-19 22:06
* @Description: 简单的懒汉式单例模式
*/
public class LazySimpleSingleton {

   private LazySimpleSingleton() {
   }

   private static LazySimpleSingleton lazySimpleSingleton = null;

   /**
    * 虽然jdk1.6版本对于synchronized进行了优化,但是在方法上直接加synchronized依旧不是最理想的懒汉式单例模式
    *
    * @return
    */
   public static synchronized LazySimpleSingleton getInstance() {
       if (lazySimpleSingleton == null) {
           lazySimpleSingleton = new LazySimpleSingleton();
       }
       return lazySimpleSingleton;
   }
}

3.2.2 双重检查锁懒汉式单例模式

/**
* @PackageName: com.raven.pattern.singleton.lazy
* @ClassName: LazyDoubleCheckSingleton
* @Blame: raven
* @Date: 2021-06-19 22:13
* @Description:双重检查锁懒汉式单例模式
*/
public class LazyDoubleCheckSingleton {

   private LazyDoubleCheckSingleton(){};
   /**
    * 加volatile关键字是为了确保多线程环境下创建对象时内存可见
    * 因为创建对象时 jvm会进行指令重排序,new LazyDoubleCheckSingleton 是多步操作 会有线程安全问题
    */
   private static volatile LazyDoubleCheckSingleton lazyDoubleCheckSingleton = null;

   /**
    * 将synchronized 锁范围缩小
    *
    * @return
    */
   public static LazyDoubleCheckSingleton getInstance() {
       if (lazyDoubleCheckSingleton == null) {
           synchronized (LazyDoubleCheckSingleton.class) {
               // 双重判断的原因是因为,假设有俩个线程A、B 如果仅仅加锁以及没有第二个if判断,
               // 线程A、B可以同时进入方法并尝试获取LazyDoubleCheckSingleton锁,
               // 假设A线程获取到LazyDoubleCheckSingleton锁,B线程就会进入等待状态,
               // 当A线程创建完对象 但getInstance方法没有return前,B线程也会获取到锁对象,并且创建对象,从而破坏单例
               if (lazyDoubleCheckSingleton == null) {
                   lazyDoubleCheckSingleton = new LazyDoubleCheckSingleton();
                   // CPU执行时候会转换为JVM指令执行
                   // 指令重排序的问题
                   // 1.分配内存给这个对象
                   // 2.初始化对象
                   // 3.将初始化好的对象和内存地址建立关联,赋值
                   // 4.用户初次访问
               }
           }
       }
       return lazyDoubleCheckSingleton;
   }
}

3.2.3 内部类的方式创建单例对象 jdk内部创建单例的方式

/**
* @PackageName: com.raven.pattern.singleton.lazy
* @ClassName: LazyInnerClassSingleton
* @Blame: raven
* @Date: 2021-06-19 22:31
* @Description: 内部类的方式创建单例对象 jdk内部创建单例的方式
*/
public class LazyInnerClassSingleton {
   /**
    * 单例模式类被反射所破坏 故加此代码禁止通过反射创建单例对象
    */
   private LazyInnerClassSingleton() {
       if (LazyHolder.singleton != null){
           throw new RuntimeException("不能通过反射创建该实例对象");
       }
   }

   /**
    * 通过内部类的方式,当LazyInnerClassSingleton 的getInstance方法被调用后,
    * JVM会通过自动加载内部类LazyHolder ,然后创建实例对象
    * 最优的方案!
    * @return
    */
   public static LazyInnerClassSingleton getInstance() {
       return LazyHolder.singleton;
   }

   private static class LazyHolder {
       private static final LazyInnerClassSingleton singleton = new LazyInnerClassSingleton();
   }
}

3.3注册式单例

package com.raven.pattern.singleton.register;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
* @PackageName: com.raven.pattern.singleton.register
* @ClassName: ContainerSingleton
* @Blame: raven
* @Date: 2021-06-21 21:24
* @Description: 容器式单例模式
*/
public class ContainerSingleton {

   private ContainerSingleton() {
   }

   private static Map<String, Object> ioc = new ConcurrentHashMap<>();

   public static Object getBean(String className) {
       synchronized (ContainerSingleton.class) {
           if (!(ioc.containsKey(className))) {
               Object obj = null;
               try {
                   obj = Class.forName(className);
                   ioc.put(className, obj);
               } catch (ClassNotFoundException e) {
                   e.printStackTrace();
               }
               return obj;
           }
       }
       return ioc.get(className);
   }
}

3.4枚举式单例

package com.raven.pattern.singleton.register;

/**
* @PackageName: com.raven.pattern.singleton.register
* @ClassName: EnumSingleton
* @Blame: raven
* @Date: 2021-06-20 10:35
* @Description: 通过枚举实现注册式单例
*/
public enum  EnumSingleton {

   /**
    * Enum的一个实例对象
    */
   INSTANCE;

   private Object data;

   public Object getData() {
       return data;
   }

   public void setData(Object data) {
       this.data = data;
   }

   public static EnumSingleton getInstance(){
       return INSTANCE;
   }
}

3.4.1枚举式单例在JDK层面防止反射破坏单例

    /**
    * 防止反射破坏单例设计
    *
    * @param args
    */
   public static void main(String[] args) throws Exception {
       Class clazz = EnumSingleton.class;
       // 通过反编译EnumSingleton 的字节码文件发现,我们只能够通过有参构造创建EnumSingleton对象
       Constructor c = clazz.getDeclaredConstructor(String.class, int.class);
       c.setAccessible(true);
       // jdk内部禁止通过反射创建枚举对象
//        if ((clazz.getModifiers() & Modifier.ENUM) != 0)
//            throw new IllegalArgumentException("Cannot reflectively create enum objects");
       // Exception in thread "main" java.lang.IllegalArgumentException: Cannot reflectively create enum objects
       EnumSingleton instance = (EnumSingleton) c.newInstance("test", 1);
       EnumSingleton instance1 = EnumSingleton.getInstance();
       System.out.println(instance == instance1);
   }

3.4.2枚举式单例在JDK层面防止反序列化破坏单例

    /**
    * 防止反序列化破坏单例设计
    *
    * @param args
    */
   public static void main(String[] args) throws Exception {
       EnumSingleton s1 = null;
       EnumSingleton s2 = EnumSingleton.getInstance();

       // 将s2写到本地磁盘
       FileOutputStream fos = new FileOutputStream("EnumSingleton.obj");
       ObjectOutputStream oos = new ObjectOutputStream(fos);
       oos.writeObject(s2);
       oos.flush();
       oos.close();

       // 将s2从磁盘读取
       FileInputStream fis = new FileInputStream("EnumSingleton.obj");
       ObjectInputStream ois = new ObjectInputStream(fis);
       s1 = (EnumSingleton) ois.readObject();
       ois.close();

//       虽然我们没有重写readResolve方法,但枚举式单例仍然可以防止反序列化破坏单例设计 主要是因为枚举式通过类的class对象+名字确定返回对象 确保唯一
//        Object obj = readObject0(false); ==》
//       checkResolve(readEnum(unshared)); ==》
//       Enum<?> en = Enum.valueOf((Class)cl, name);

       System.out.println(s1);
       System.out.println(s2);
       System.out.println(s1 == s2);
   }

3.5ThreadLocal单例

package com.raven.pattern.singleton.register;

/**
* @PackageName: com.raven.pattern.singleton.register
* @ClassName: ThreadLocalSingleton
* @Blame: raven
* @Date: 2021-06-21 21:43
* @Description: ThreadLocal线程间单例 可用于实现ORM动态切换数据源
*/
public class ThreadLocalSingleton {
  private ThreadLocalSingleton() {
  }

  private static final ThreadLocal<ThreadLocalSingleton> threadLocalInstance = ThreadLocal.withInitial(ThreadLocalSingleton::new);

  public static ThreadLocalSingleton getInstance() {
      return threadLocalInstance.get();
  }
}

4.模拟反射破坏单例问题

package com.raven.pattern.singleton.lazy;

import java.lang.reflect.Constructor;

/**
 * @PackageName: com.raven.pattern.singleton.lazy
 * @ClassName: ReflectBrokeSingletonTest
 * @Blame: raven
 * @Date: 2021-06-20 9:21
 * @Description: 模拟反射破坏单例问题
 */
public class ReflectBrokeSingletonTest {

    public static void main(String[] args) throws Exception {
        // 虽然我们通过代码的方式实现了单例设计,但当调用者不走寻常路,使用反射是可以破坏我们单例设计的
        Class<?> clazz = LazyInnerClassSingleton.class;
        Constructor c = clazz.getDeclaredConstructor(null);
        // 强吻
        c.setAccessible(true);
        // 通过反射创建的对象
        Object o1 = c.newInstance();
        // 通过单例设计模式获取到的对象
        LazyInnerClassSingleton o2 = LazyInnerClassSingleton.getInstance();
        System.out.println(o1 == o2);

//        LazyInnerClassSingleton o3 = LazyInnerClassSingleton.getInstance();
//        System.out.println(o2 == o3);
    }
}

防止反射破坏单例(解决方案)

在私有构造方法中增加校验

    /**
     * 单例模式类被反射所破坏 故加此代码禁止通过反射创建单例对象
     */
    private LazyInnerClassSingleton() {
        if (LazyHolder.singleton != null){
            throw new RuntimeException("不能通过反射创建该实例对象");
        }
    }

5.模拟反序列化破坏单例设计

package com.raven.pattern.singleton.serialiable;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

/**
 * @PackageName: com.raven.pattern.singleton.serialiable
 * @ClassName: SerializableBrokeSingletonTest
 * @Blame: raven
 * @Date: 2021-06-20 9:43
 * @Description: 模拟反序列化破坏单例设计
 */
public class SerializableBrokeSingletonTest {

    public static void main(String[] args) throws Exception {
        SerializableSingleton s1 = null;
        SerializableSingleton s2 = SerializableSingleton.getInstance();

        // 将s2写到本地磁盘
        FileOutputStream fos = new FileOutputStream("SerializableSingleton.obj");
        ObjectOutputStream oos = new ObjectOutputStream(fos);
        oos.writeObject(s2);
        oos.flush();
        oos.close();

        // 将s2从磁盘读取
        FileInputStream fis = new FileInputStream("SerializableSingleton.obj");
        ObjectInputStream ois = new ObjectInputStream(fis);
        s1 = (SerializableSingleton) ois.readObject();
        ois.close();

        // 发现单例被反序列化所破坏 需要在单例中重写 readResolve防止序列化破坏单例
        System.out.println(s1);
        System.out.println(s2);
        System.out.println(s1 == s2);
    }
}

package com.raven.pattern.singleton.serialiable;

import java.io.Serializable;

/**
 * @PackageName: com.raven.pattern.singleton.serialiable
 * @ClassName: SerializableSingleton
 * @Blame: raven
 * @Date: 2021-06-20 9:35
 * @Description: 反序列化导致单例破坏
 */
public class SerializableSingleton implements Serializable {

    // 序列化:
    // 序列化就是把内存中的状态通过转换为字节码的形式
    // 从而转化一个IO流,写入到其他地方(可以是磁盘,网络IO)
    // 内存中的状态给永久的保存下来了

    // 反序列化
    // 将已经持久化的字节码内容,转换为IO流
    // 通过IO流的读取,进而将读取的内容转换为Java对象
    // 在转换过程中会重新创建对象new

    private SerializableSingleton() {
    }

    private static final SerializableSingleton SINGLETON = new SerializableSingleton();

    public static SerializableSingleton getInstance() {
        return SINGLETON;
    }

    /**
     * 阅读源码可知 反序列化的readObject底层原理 只需重写readResolve 即可防止反序列化对象破坏单例
     * readObject ->
     * readObject0 ->
     * readOrdinaryObject ->
     * // 对象是否已实例化,是就new一个对象出来
     * obj = desc.isInstantiable() ? desc.newInstance() : null ->
     * if (obj != null && handles.lookupException(passHandle) == null && desc.hasReadResolveMethod()) ->
     * readResolveMethod = getInheritableMethod(cl, "readResolve", null, Object.class); ->
     * // 不为空且已经实现了ReadResolve方法 就执行该方法 并返回对象
     * Object rep = desc.invokeReadResolve(obj)
     *
     * @return
     */
    private Object readResolve() {
        return SINGLETON;
    }

}

防止反序列化单例(解决方案)

在单例类中重写readResolve方法

      /**
     * 阅读源码可知 反序列化的readObject底层原理 只需重写readResolve 即可防止反序列化对象破坏单例
     * readObject ->
     * readObject0 ->
     * readOrdinaryObject ->
     * // 对象是否已实例化,是就new一个对象出来
     * obj = desc.isInstantiable() ? desc.newInstance() : null ->
     * if (obj != null && handles.lookupException(passHandle) == null && desc.hasReadResolveMethod()) ->
     * readResolveMethod = getInheritableMethod(cl, "readResolve", null, Object.class); ->
     * // 不为空且已经实现了ReadResolve方法 就执行该方法 并返回对象
     * Object rep = desc.invokeReadResolve(obj)
     *
     * @return
     */
    private Object readResolve() {
        return SINGLETON;
    }

6.总结

6.1单例设计模式的缺点

6.1.1 没有接口,拓展困难

6.1.2 如果要拓展单例对象,只有修改代码,没有其他途径。

6.2单例设计模式的优点

6.2.1 在内存中只有一个实例,减少了内存开销

6.2.2 可以避免对资源的多重占用

6.2.3 设置全局访问点,严格控制访问

6.3单例模式学习的重点

6.3.1 私有构造器

6.3.2 保证线程安全

6.3.3 延迟加载

6.3.4 防止序列化和反序列化破坏单例

6.3.5 防御反射攻击单例

序号设计模式名称博客链接demo 代码链接
1.工厂设计模式https://blog.youkuaiyun.com/weixin_44993313/article/details/118046794https://github.com/Gaoning97/coding-life/tree/main/spring/src/main/java/com/raven/pattern/factory
2.单例设计模式https://blog.youkuaiyun.com/weixin_44993313/article/details/118112188https://github.com/Gaoning97/coding-life/tree/main/spring/src/main/java/com/raven/pattern/singleton
3.委派模式&策略模式https://blog.youkuaiyun.com/weixin_44993313/article/details/119301564https://github.com/Gaoning97/coding-life/tree/main/spring/src/main/java/com/raven/pattern/strategy
4.模板模式&适配器模式https://blog.youkuaiyun.com/weixin_44993313/article/details/119301564https://github.com/Gaoning97/coding-life/tree/main/spring/src/main/java/com/raven/pattern/template
5.装饰者模式&观察者模式https://blog.youkuaiyun.com/weixin_44993313/article/details/119512428https://github.com/Gaoning97/coding-life/tree/main/spring/src/main/java/com/raven/pattern/decorator
6.设计模式总结https://blog.youkuaiyun.com/weixin_44993313/article/details/119547100/
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值