设计模式之单例设计模式

目录

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 防御反射攻击单例

打赏一个呗

取消

感谢您的支持,我会继续努力的!

扫码支持
扫码支持
扫码打赏,你说多少就多少

打开支付宝扫一扫,即可进行扫码打赏哦