🍰 单例模式

吞佛童子2022年6月20日
  • 设计模式
  • 创建型
  • 单例模式
大约 7 分钟

🍰 单例模式

1. 概述

1) 是什么

  • 保证一个类仅有一个实例,并提供一个访问它的全局访问点

2) 优点

  • 在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例(比如网站首页页面缓存)
  • 避免对资源的多重占用(比如写文件操作)
  • 省去了new操作符,降低了系统内存的使用频率,减轻GC压力
  • 系统中某些类,如spring里的controller,控制着处理流程,如果该类可以创建多个的话,系统完全乱了

3) 缺点


2. 代码示例

1) 场景 & 目标

  • 一个类的对象的产生是由类构造函数来完成的。如果一个类对外提供了public的构造方法,那么外界就可以任意创建该类的对象。
  • 所以,如果想限制对象的产生,一个办法就是将构造函数变为私有的(至少是受保护的),使外面的类不能通过引用来产生对象。
  • 没有 public setter(),外部类无法调用 setter() 创建该实例
  • 同时为了保证类的可用性,就必须提供一个自己的对象以及访问这个对象的静态方法
  • 提供一个 public static getter() 获取唯一的这个实例

2) 饿汉式

  • 他想要用到这个实例的时候就能够立即拿到,而不需要任何等待时间
  • 通过 static 的静态初始化方式,在该类第一次被加载的时候,就有一个 SimpleSingleton 的实例被创建出来了。
  • 由于该实例在类被加载的时候就创建出来了,所以也避免了线程安全问题 - 类加载过程中 JVM 保证该过程线程安全
  • 适用场景:
    • 在很多电商场景,如果这个数据是经常访问的热点数据,那我就可以在系统启动的时候使用饿汉模式提前加载(类似缓存的预热)
    • 这样哪怕是第一个用户调用都不会存在创建开销,而且调用频繁也不存在内存浪费了

3) 懒汉式

  • 适用场景:
    • 用在不怎么热的地方,比如那个数据你不确定很长一段时间是不是有人会调用,那就用懒汉,
    • 如果你使用了饿汉,但是过了几个月还没人调用,提前加载的类在内存中是有资源浪费的

volatile 发挥的作用

  1. 防止指令重排
  2. 保证可见性
  3. instance = new Singleton() 分以下几个步骤:
    • 为 new Singleton() 分配内存
    • 初始化
    • 将 instance 指向这块内存
  4. 初始化 & 将 instance 指向这块内存可能顺序发生改变
  5. 此时若线程 A 先修改指针指向,初始化还未完成
  6. 线程 B 进入判断条件,发现 instance != null 直接得到该还未初始化完成的 instance 实例,并立即使用,就会发生错误

序列化的问题

  1. 序列化 & 反序列化是通过 ObjectOutputStream & ObjectInputStream 方法实现的,
  2. ObjectInputStream 的 readObject() 执行情况如下:
  • img_1.png
  1. readOrdinaryObject(boolean unshared)
public class ObjectInputStream extends InputStream implements ObjectInput, ObjectStreamConstants
{
    public final Object readObject() throws IOException, ClassNotFoundException {
        return readObject(Object.class);
    }
    
    private final Object readObject(Class<?> type) throws IOException, ClassNotFoundException
    {
        if (enableOverride) {
            return readObjectOverride();
        }

        if (! (type == Object.class || type == String.class))
            throw new AssertionError("internal error");

        // if nested read, passHandle contains handle of enclosing object
        int outerHandle = passHandle;
        try {
            Object obj = readObject0(type, false); // --> 继续跟入
            handles.markDependency(outerHandle, passHandle);
            ClassNotFoundException ex = handles.lookupException(passHandle);
            if (ex != null) {
                throw ex;
            }
            if (depth == 0) {
                vlist.doCallbacks();
            }
            return obj;
        } finally {
            passHandle = outerHandle;
            if (closed && depth == 0) {
                clear();
            }
        }
    }
    
    private Object readObject0(Class<?> type, boolean unshared) throws IOException {
        boolean oldMode = bin.getBlockDataMode();
        // ...
        try {
            switch (tc) {
                // ...

                case TC_ENUM:
                    if (type == String.class) {
                        throw new ClassCastException("Cannot cast an enum to java.lang.String");
                    }
                    return checkResolve(readEnum(unshared));

                case TC_OBJECT:
                    if (type == String.class) {
                        throw new ClassCastException("Cannot cast an object to java.lang.String");
                    }
                    return checkResolve(readOrdinaryObject(unshared)); // --> 继续跟入

                // ...

                default:
                    throw new StreamCorruptedException(
                        String.format("invalid type code: %02X", tc));
            }
        } finally {
            depth--;
            bin.setBlockDataMode(oldMode);
        }
    }
    
    private Object readOrdinaryObject(boolean unshared) throws IOException
    {
        // ...

        Object obj;
        try { // 正常情况下,会通过 反射 生成一个新的对象,因此会破坏单例模式
            obj = desc.isInstantiable() ? desc.newInstance() : null;
        } catch (Exception ex) {
            throw (IOException) new InvalidClassException(
                desc.forClass().getName(),
                "unable to create instance").initCause(ex);
        }

        // ...

        if (obj != null &&
            handles.lookupException(passHandle) == null &&
            desc.hasReadResolveMethod()) // 如果有 readResolve() 方法
        {
            // 通过 反射 调用要被反序列化的类的 readResolve() 方法
            // 因此,如果重写了 readResolve() 方法,在该方法中返回的是 原单例对象,那么这里获得的就是 原单例对象
            Object rep = desc.invokeReadResolve(obj); 
            if (unshared && rep.getClass().isArray()) {
                rep = cloneArray(rep);
            }
            if (rep != obj) {
                if (rep != null) {
                    if (rep.getClass().isArray()) {
                        filterCheck(rep.getClass(), Array.getLength(rep));
                    } else {
                        filterCheck(rep.getClass(), -1);
                    }
                }
                handles.setObject(passHandle, obj = rep); // 这里将 rep 赋值给 obj,然后返回 obj
            }
        }

        return obj;
    }
}

4) 静态内部类

  • 也是通过类加载机制保证线程安全

5) 枚举

// 构造函数默认为 private,且只能为 private
public enum  Singleton {

    INSTANCE;
    public Singleton getInstance() { 
        return INSTANCE;
    }
}

枚举保证线程安全

  1. 定义一个枚举类
public enum t {
    SPRING,SUMMER,AUTUMN,WINTER;
}
  1. 反编译
// T 为 final 修饰,不可被继承
public final class T extends Enum
{
    private T(String s, int i)
    {
        super(s, i);
    }
    public static T[] values()
    {
        T at[];
        int i;
        T at1[];
        System.arraycopy(at = ENUM$VALUES, 0, at1 = new T[i = at.length], 0, i);
        return at1;
    }

    public static T valueOf(String s)
    {
        return (T)Enum.valueOf(demo/T, s);
    }

    public static final T SPRING; // SPRING 属性被 static final 修饰,属于类静态变量 & 只能赋值一次
    public static final T SUMMER;
    public static final T AUTUMN;
    public static final T WINTER;
    private static final T ENUM$VALUES[];
    static
    {
        SPRING = new T("SPRING", 0); // 类加载时被初始化进行赋值
        SUMMER = new T("SUMMER", 1);
        AUTUMN = new T("AUTUMN", 2);
        WINTER = new T("WINTER", 3);
        ENUM$VALUES = (new T[] {
            SPRING, SUMMER, AUTUMN, WINTER
        });
    }
}
  1. 解释:
  • 假如说我们现在需要 SPRING 这个枚举对象
  • 通过 SPRING = new T("SPRING", 0); 得到
  • 可以看出 SPRING = new T("SPRING", 0); 首先是属于 static 静态代码块的一部分,所以该属性会在类加载中被初始化,且线程安全,只加载一次
  • 同时,SPRING 属于 T 类,而 T 对象类被 final 修饰,表明该类不可被继承

枚举保证序列化安全

  1. Java规范要求:
  • 在序列化的时候Java仅仅是将枚举对象的name属性输出到结果中,
  • 反序列化的时候则是通过java.lang.Enum的valueOf方法来根据名字查找枚举对象
  • 且编译器是不允许任何对这种序列化机制的定制的
  • 因此也不需要走 writeObjectreadObjectreadObjectNoDatawriteReplace readResolve 等方法
  • 从而也就不会通过反射生成新的实例了
  1. 所以说,枚举有自己的序列化 & 反序列化方式

3. 应用

1) Bean

  • Spring依赖注入Bean实例默认是单例的
  • 参见 框架下的循环依赖中的 getSingleton() 方法,里面通过 双重判断 + 锁 保证创建的是单例
上次编辑于: 2022/10/10 下午8:43:48
贡献者: liuxianzhishou