创建型模式-单例模式

2020-03-01 1431点热度 0人点赞 0条评论
设计模式系列文章:https://zhum.in/blog/category/notes/design-pattern
Demo code:https://github.com/njZhuMin/BlogSampleCode

为什么要用单例模式

单例模式(Singleton Pattern)是指确保一个类在任何情况下都只有一个实例,提供一个全局访问点并隐藏其所有的构造方法。

常见的单例模式场景有:

  • ServletContext
  • ServletConfig
  • ApplicationContext
  • DBPool

饿汉式单例

单例类首次创建时就创建单例
public class HungrySingleton {
    private static final HungrySingleton sharedInstance = new HungrySingleton();
    private HungrySingleton() {}

    public HungrySingleton getInstance() {
        return sharedInstance;
    }
}

优点:

  • 执行效率高
  • 没有任何的锁

缺点:

  • 某些情况下可能会造成内存浪费(类的数量膨胀)
  • 启动慢

什么情况下不适用:
例如 Spring 作为 IOC 容器,加载一个拥有大量的类的工程时,可能会创建出大量没有被使用到的单例。

懒汉式单例

在被外部类调用时再创建实例

原型

public class LazySimpleSingleton {
    private static LazySimpleSingleton sharedInstance;
    private LazySimpleSingleton() {}

    public static LazySimpleSingleton getInstance() {
        if (sharedInstance == null) {
            sharedInstance = new LazySimpleSingleton();
        }
        return sharedInstance;
    }
}

优点:

  • 节省了不必要的内存消耗

缺点:

  • 线程不安全,并发调用会导致创建多个实例

测试代码:

public class LazySimpleSingletonTest {
    public static void main(String[] args) {
        Thread thread1 = new Thread(new ExecutorThread());
        Thread thread2 = new Thread(new ExecutorThread());
        thread1.start();
        thread2.start();
        System.out.println("End");
    }
}

class ExecutorThread implements Runnable {
    @Override
    public void run() {
        LazySimpleSingleton instance = LazySimpleSingleton.getInstance();
        System.out.println(Thread.currentThread().getName() + ":" + instance);
    }
}

这段代码可能会出现三个结果:

  1. 正常顺序执行,两次输出相同的实例
  2. 一个线程在 sharedInstance = new LazySimpleSingleton(); 时被抢占了 CPU,但在打印变量前恢复了执行。此时另一个线程其实已经创建了一个实例 1,此时因为判空的判断已经过了,所以还是会再执行一遍新建实例并覆盖了前一个实例。然后两个线程分别继续打印,虽然打印出了同样的结果,但是新建实例执行了两次,从定义上来看不是线程安全的。
  3. 同上,第二个线程在第一个线程打印完之后才恢复执行新建语句。此时两个线程打印出不同的实例对象。

加锁

可以通过加锁阻塞来解决线程不安全的问题,但是造成了类级别的同步问题。

public static synchronized LazySimpleSingleton getInstance() {
    if (sharedInstance == null) {
        sharedInstance = new LazySimpleSingleton();
    }
    return sharedInstance;
}

双重检查锁

我们可以细化锁的粒度,但是这样造成了在判空的时候出现线程不安全。因此要求在进入和回到创建实例的时候必须双重检查。

public class LazyDoubleCheckSingleton {

    private volatile static LazyDoubleCheckSingleton sharedInstance;

    private LazyDoubleCheckSingleton() {}

    public static LazyDoubleCheckSingleton getInstance() {
        // check if need block
        if (sharedInstance == null) {
            synchronized (LazySimpleSingleton.class) {
                // check again if need to create instance when re-gained lock
                if (sharedInstance == null) {
                    sharedInstance = new LazyDoubleCheckSingleton();
                    // watch out volatile re-arrange
                }
            }
        }
        return sharedInstance;
    }
}

内部静态类写法

这种写法本质上其实是一种懒汉式加载的单例模式。

这里利用了 Java 自身语言特点的一个 trick,即在 ClassLoader 加载一个类的时候,其内部静态类不是在一开始就被创建,而是在使用的时候才创建。这样的语言特点允许我们使用更加优雅的实现:

public class LazyStaticInnerClassSingleton {

    private LazyStaticInnerClassSingleton() {}

    private static LazyStaticInnerClassSingleton getInstance() {
        return LazyHolder.INSTANCE;
    }

    private static class LazyHolder {
        private static final LazyStaticInnerClassSingleton INSTANCE = new LazyStaticInnerClassSingleton();
    }
}

优点:

  • 写法优雅,利用了 Java 语言特点
  • 性能高,避免了内存浪费

缺点:

  • 可以被反射破坏单例

反射创建多个实例:

public class ReflectionTest {
    public static void main(String[] args) {
        try {
            Class<?> clazz = LazyStaticInnerClassSingleton.class;
            Constructor constructor = clazz.getDeclaredConstructor();
            constructor.setAccessible(true);
            Object instance1 = constructor.newInstance();
            Object instance2 = constructor.newInstance();
            System.out.println(instance1);
            System.out.println(instance2);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

一种简单的解决方法,在构造函数里抛出异常来阻止通过反射来创建实例。

public class LazyStaticInnerClassSingleton {

    private LazyStaticInnerClassSingleton() {
        if (LazyHolder.INSTANCE != null) {
            throw new RuntimeException("Creating multiple instance by reflection is not allowed.");
        }
    }

    public static final LazyStaticInnerClassSingleton getInstance() {
        return LazyHolder.INSTANCE;
    }

    private static class LazyHolder {
        private static final LazyStaticInnerClassSingleton INSTANCE = new LazyStaticInnerClassSingleton();
    }
}

但是这种写法看起来有些不太优雅,在一个私有构造函数中抛异常。在继续优化之前,我们先看另一个问题:序列化也可能破坏单例模式。

序列化破坏单例

一个单例对象创建好后,有时候需要将对象序列化然后写入磁盘,下次使用时再从磁盘中读取对象并进行反序列化,将其转化为内存对象。反序列化后的对象会重新分配内存,即重新创建。如果序列化的目标对象为单例对象,就违背了单例模式的初衷,相当于破坏了单例。

public class SerializableSingleton implements Serializable {
    private final static SerializableSingleton INSTANCE = new SerializableSingleton();
    private SerializableSingleton() {}

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

public class SerializableSingletonTest {
    public static void main(String[] args) {
        SerializableSingleton instance1 = null;
        SerializableSingleton instance2 = SerializableSingleton.getInstance();
        FileOutputStream fos = null;
        try {
            fos = new FileOutputStream("SerializableSingleton.obj");
            ObjectOutputStream oos = new ObjectOutputStream(fos);
            oos.writeObject(instance2);
            oos.flush();
            oos.close();

            FileInputStream fis = new FileInputStream("SerializableSingleton.obj");
            ObjectInputStream ois = new ObjectInputStream(fis);
            instance1 = (SerializableSingleton)ois.readObject();
            ois.close();

            System.out.println(instance1);
            System.out.println(instance2);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

从这个例子中可以看出,一个对象在被序列化之后再反序列化,得到的对象不再是原来的对象,从而破坏了单例模式。解决的方法是实现 Serializable 接口的 readResolve 方法:

public class SerializableSingleton implements Serializable {
    private final static SerializableSingleton INSTANCE = new SerializableSingleton();
    private SerializableSingleton() {}

    public static SerializableSingleton getInstance() {
        return INSTANCE;
    }

    private Object readResolve() {
        return INSTANCE;
    }
}

为什么这样一个 undocumented 的方法就可以解决反序列化的问题了呢,原因是 JDK 在实现序列化和反序列化的过程中,会检查这个(隐藏的后门)方法。

public class ObjectInputStream extends InputStream implements ObjectInput, ObjectStreamConstants {
    public final Object readObject() throws IOException, ClassNotFoundException {
        if (this.enableOverride) {
            return this.readObjectOverride();
        } else {
            int outerHandle = this.passHandle;
            Object var4;
            try {
                Object obj = this.readObject0(false);
                // ...
            }
            return var4;
        }
    }
}

private Object readObject0(Class<?> type, boolean unshared) throws IOException {
    boolean oldMode = bin.getBlockDataMode();
    if (oldMode) {
        // ...
    }

    byte tc;
    while ((tc = bin.peekByte()) == TC_RESET) {
        bin.readByte();
        handleReset();
    }

    depth++;
    totalObjectRefs++;
    try {
        switch (tc) {
            // ...

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

            //...
        }
    } finally {
        depth--;
        bin.setBlockDataMode(oldMode);
    }
}

继续看到 readOrdinaryObject 方法:

private Object readOrdinaryObject(boolean unshared) throws IOException {
    // ...
    Object obj;
    try {
        obj = desc.isInstantiable() ? desc.newInstance() : null;
    }
    // ...
    handles.finish(passHandle);
    if (obj != null &&
        handles.lookupException(passHandle) == null &&
        desc.hasReadResolveMethod())
    {
        Object rep = desc.invokeReadResolve(obj);
        if (unshared && rep.getClass().isArray()) {
            rep = cloneArray(rep);
        }
        if (rep != obj) {
            // Filter the replacement object
            if (rep != null) {
                if (rep.getClass().isArray()) {
                    filterCheck(rep.getClass(), Array.getLength(rep));
                } else {
                    filterCheck(rep.getClass(), -1);
                }
            }
            handles.setObject(passHandle, obj = rep);
        }
    }
    return obj;
}

这里有一个判断:

public class ObjectStreamClass implements Serializable {
    boolean hasReadResolveMethod() {
        requireInitialized();
        return (readResolveMethod != null);
    }
}

private ObjectStreamClass(final Class<?> cl) {
    // ...
    readResolveMethod = getInheritableMethod(cl, "readResolve", null, Object.class);
    // ...
    initialized = true;
}

上面的逻辑其实就是通过反射找到一个无参的 readResolve()方法,并且保存下来。

回到 ObjectInputStreaminvokeReadResolve()方法,直接通过反射执行了 readResolve()的方法调用:

Object invokeReadResolve(Object obj)
        throws IOException, UnsupportedOperationException {
    requireInitialized();
    if (readResolveMethod != null) {
        try {
            return readResolveMethod.invoke(obj, (Object[]) null);
        }
        // ...
    }
}

至此,通过 JDK 源码分析我们可以看出,通过实现 readResolve()方法可以解决单例模式被破坏问题的原因。

但其实,在 readOrdinaryObject()方法第一行:

try {
    obj = desc.isInstantiable() ? desc.newInstance() : null;
}

这个实例已经被创建出来了,只是在对象返回前被覆盖了:

Object rep = desc.invokeReadResolve(obj);

因此实际上,反序列化过程中这个对象还是实例化了两次。可以预见如果创建对象的动作发生频率很快,就意味着内存分配的开销也会随之增大。

注册式单例

注册式单例模式又称登记式单例模式,就是将每一个实例都登记到某一个地方,使用唯一的标识获取实例。注册式单例模式有两种:一种为枚举式单例模式,另一种为容器式单例模式。

枚举式单例

枚举式单例即利用枚举类自身的特性实现了单例模式。这也是《Effective Java》中第 2 章第 3 条中推荐的做法。
public enum EnumSingleton {
    INSTANCE;

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

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

测试反射破坏枚举单例

测试一下反射来创建枚举类的实例:

public class EnumSingletonTest {
    public static void main(String[] args) {
        try {
            EnumSingleton instance = EnumSingleton.getInstance();
            instance.setData(new Object());

            Class clazz = EnumSingleton.class;
            Constructor constructor = clazz.getDeclaredConstructor();
            constructor.setAccessible(true);
            Object object1 = constructor.newInstance();
            System.out.println(object1);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

这里报了一个方法找不到的错误:

java.lang.NoSuchMethodException: example.Singleton.enumSingleton.EnumSingleton.<init>()

我们进到 Enum 类中查看一下,发现原来是因为 Enum 类中没有实现无参构造方法,只有一个 private Enum(String, int)的构造方法。那我们来尝试使用这个含参构造方法来构造:

public class EnumSingletonTest {
    public static void main(String[] args) {
        try {
            // ...
            Constructor constructor = clazz.getDeclaredConstructor(String.class, int.class);
            constructor.setAccessible(true);
            Object object1 = constructor.newInstance();
            System.out.println(object1);
        }
    }
}

这是直接给出了明确的错误提示:

java.lang.IllegalArgumentException: Cannot reflectively create enum objects
    at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:484)

我们在 Constructor.newInstance()方法中继续研究出现这个错误的原因:

public T newInstance(Object... initargs) throws InstantiationException, IllegalAccessException,IllegalArgumentException, InvocationTargetException {
    if ((this.clazz.getModifiers() & Modifier.ENUM) != 0) {
        throw new IllegalArgumentException("Cannot reflectively create enum objects");
    }
}

这里感觉和我们之前不优雅的写法有异曲同工之处:

private LazyStaticInnerClassSingleton() {
    if (LazyHolder.INSTANCE != null) {
        throw new RuntimeException("Creating multiple instance by reflection is not allowed.");
    }
}

那么这种做法优雅在哪儿呢,就优雅在他是 JDK 官方实现!

测试序列化破坏枚举单例

同样的,我们也来测试一下序列化对枚举式单例的影响:

private static void testSerialization() {
    EnumSingleton instance1 = null;
    EnumSingleton instance2 = EnumSingleton.getInstance();
    FileOutputStream fos = null;
    try {
        fos = new FileOutputStream("EnumSingleton.obj");
        ObjectOutputStream oos = new ObjectOutputStream(fos);
        oos.writeObject(instance2);
        oos.flush();
        oos.close();
        FileInputStream fis = new FileInputStream("EnumSingleton.obj");
        ObjectInputStream ois = new ObjectInputStream(fis);
        instance1 = (EnumSingleton)ois.readObject();
        ois.close();
        System.out.println(instance1.hashCode());
        System.out.println(instance2.hashCode());
    } catch (Exception e) {
        e.printStackTrace();
    }
}

发现反序列化后的对象竟然自动就是同一个对象,不再需要我们去实现 readResolve 方法了。这是为什么呢,我们还是要去 ObjectInputStream.readObject()这个方法一探究竟。

private Object readObject0(boolean unshared) throws IOException {
    case TC_ENUM:
        return checkResolve(readEnum(unshared));
}

private Enum<?> readEnum(boolean unshared) throws IOException {
    // ...
    String name = readString(false);
    Enum<?> result = null;
    Class<?> cl = desc.forClass();
    if (cl != null) {
        try {
            @SuppressWarnings("unchecked")
            Enum<?> en = Enum.valueOf((Class)cl, name);
            result = en;
        } catch (IllegalArgumentException ex) {
            throw (IOException) new InvalidObjectException(
                "enum constant " + name + " does not exist in " +
                cl).initCause(ex);
        }
        if (!unshared) {
            handles.setObject(enumHandle, result);
        }
    }
    handles.finish(enumHandle);
    passHandle = enumHandle;
    return result;
}

这里关键的就是这个赋值,调用了 Enum.valueOf()方法:

public abstract class Enum<E extends Enum<E>>
        implements Comparable<E>, Serializable {
    public static <T extends Enum<T>> T valueOf(Class<T> enumType,
                                                String name) {
        T result = enumType.enumConstantDirectory().get(name);
        if (result != null)
            return result;
        if (name == null)
            throw new NullPointerException("Name is null");
        throw new IllegalArgumentException(
            "No enum constant " + enumType.getCanonicalName() + "." + name);
    }
}

这里的 enumType.enumConstantDirectory()看起来是从当前这个 Class 中的一个静态字典中去取得了实例。

public final class Class<T> implements Serializable, GenericDeclaration, Type, AnnotatedElement {
    private transient volatile Map<String, T> enumConstantDirectory;
}

反编译:

public final class EnumSingleton extends java.lang.Enum<EnumSingleton> {
  public static final EnumSingleton INSTANCE;
  public static EnumSingleton[] values();
  public static EnumSingleton valueOf(java.lang.String);
  public java.lang.Object getData();
  public void setData(java.lang.Object);
  public static EnumSingleton getInstance();
  static {};
}

所以,枚举式单例模式是在类初始化时就给静态变量 INSTANCE 进行了赋值,是饿汉式单例模式的实现,因此自然也是线程安全的。

容器式单例

容器式单例将每一个实例都缓存到统一的容器中,使用唯一标识(类名)获取实例。

受枚举式单例的启发,我们能不能实现一种结合了容器,却又是懒加载模式(而非饿汉式)的单例模式,从而避免需要管理大量对象时饿汉式模式造成的内存浪费与性能问题呢?

public class ContainerSingleton {

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

    private ContainerSingleton() {}

    public static Object getInstance(Class<?> clazz) {
        Object instance = null;
        if (ioc.containsKey(clazz.getName())) {
            return ioc.get(clazz.getName());
        }

        try {
            instance = clazz.getDeclaredConstructor().newInstance();
            ioc.put(clazz.getName(), instance);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return instance;
    }
}

public class ContainerSingletonTest {
    public static void main(String[] args) {
        Object instance1 = ContainerSingleton.getInstance(Pojo.class);
        Object instance2 = ContainerSingleton.getInstance(Pojo.class);
        System.out.println(instance1);
        System.out.println(instance2);
    }
}

容器式单例模式适用于实例非常多的情况,便于管理。但它是非线程安全的。

如何让容器式单例变得线程安全呢?我们可以参考 Spring 中``类的实现,其实就是给方法加了个锁:

public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport implements ConfigurableBeanFactory {
    protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,
            @Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {

        final String beanName = transformedBeanName(name);
        Object bean;

        // Eagerly check singleton cache for manually registered singletons.
        Object sharedInstance = getSingleton(beanName);
        if (sharedInstance != null && args == null) {
            if (logger.isTraceEnabled()) {
                if (isSingletonCurrentlyInCreation(beanName)) {
                    logger.trace("Returning eagerly cached instance of singleton bean '" + beanName +
                            "' that is not fully initialized yet - a consequence of a circular reference");
                }
                else {
                    logger.trace("Returning cached instance of singleton bean '" + beanName + "'");
                }
            }
            bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
        }
        // ...
    }

    protected Object getObjectForBeanInstance(
            Object beanInstance, String name, String beanName, @Nullable RootBeanDefinition mbd) {

        // ...
        Object object = null;
        if (mbd != null) {
            mbd.isFactoryBean = true;
        }
        else {
            object = getCachedObjectForFactoryBean(beanName);
        }
        if (object == null) {
            // Return bean instance from factory.
            FactoryBean<?> factory = (FactoryBean<?>) beanInstance;
            // Caches object obtained from FactoryBean if it is a singleton.
            if (mbd == null && containsBeanDefinition(beanName)) {
                mbd = getMergedLocalBeanDefinition(beanName);
            }
            boolean synthetic = (mbd != null && mbd.isSynthetic());
            object = getObjectFromFactoryBean(factory, beanName, !synthetic);
        }
        return object;
    }
}

这里实际有两个分支,

object = getCachedObjectForFactoryBean(beanName);
object = getObjectFromFactoryBean(factory, beanName, !synthetic);

分别看这两个方法的实现:

public abstract class FactoryBeanRegistrySupport extends DefaultSingletonBeanRegistry {
    protected Object getCachedObjectForFactoryBean(String beanName) {
        return this.factoryBeanObjectCache.get(beanName);
    }

    protected Object getObjectFromFactoryBean(FactoryBean<?> factory, String beanName, boolean shouldPostProcess) {
        if (factory.isSingleton() && containsSingleton(beanName)) {
            synchronized (getSingletonMutex()) {
                Object object = this.factoryBeanObjectCache.get(beanName);
                if (object == null) {
                    object = doGetObjectFromFactoryBean(factory, beanName);
                    // Only post-process and store if not put there already during getObject() call above
                    // (e.g. because of circular reference processing triggered by custom getBean calls)
                    Object alreadyThere = this.factoryBeanObjectCache.get(beanName);
                    if (alreadyThere != null) {
                        object = alreadyThere;
                    }
                    else {
                        // ...
                    }
                }
                return object;
            }
        }
        else {
            Object object = doGetObjectFromFactoryBean(factory, beanName);
            // ...
            return object;
        }
    }
}

真正的对象就是从这里获得的:

this.factoryBeanObjectCache.get(beanName);

public abstract class FactoryBeanRegistrySupport extends DefaultSingletonBeanRegistry {
    /** Cache of singleton objects created by FactoryBeans: FactoryBean name to object. */
    private final Map<String, Object> factoryBeanObjectCache = new ConcurrentHashMap<>(16);
}

至此,我们印证了 Spring 中的 BeanFactory 就是使用了容器式的单例模式,并且是通过双重检查锁的方式来保证了线程的安全。

ThreadLocal 单例

ThreadLocal 不能保证其创建的对象是全局唯一的,但是能保证在单个线程中是唯一的,天生是线程安全的。

public class ThreadLocalSingleton {

    // Lambda initialization for Java 8+
    private static final ThreadLocal<ThreadLocalSingleton> threadLocalInstance =
            ThreadLocal.withInitial(() -> new ThreadLocalSingleton());

//    private static final ThreadLocal<ThreadLocalSingleton> threadLocalInstance =
//            new ThreadLocal<ThreadLocalSingleton>() {
//                @Override
//                protected ThreadLocalSingleton initialValue() {
//                    return new ThreadLocalSingleton();
//                }
//            };

    private ThreadLocalSingleton() {}

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

public class ThreadLocalSingletonTest {
    public static void main(String[] args) {
        System.out.println(ThreadLocalSingleton.getInstance());
        System.out.println(ThreadLocalSingleton.getInstance());
        System.out.println(ThreadLocalSingleton.getInstance());

        Thread t1 = new Thread(new ExecutorThread());
        Thread t2 = new Thread(new ExecutorThread());
        t1.start();
        t2.start();
        System.out.println("End"); }
}

class ExecutorThread implements Runnable {
    @Override
    public void run() {
        ThreadLocalSingleton instance = ThreadLocalSingleton.getInstance();
        System.out.println(Thread.currentThread().getName() + ":" + instance);
    }
}

我们发现,在主线程中无论调用多少次,获取到的实例都是同一个,而在两个子线程中分别获取到了不同的实例。那么 ThreadLocal 是如何实现这样的效果的呢?

我们知道,单例模式为了达到线程安全的目的,会给方法上锁,以时间换空间。 ThreadLocal 则是将所有的对象全部放在 ThreadLocalMap 中,为每个线程都提供一个对象,实际上是以空间换时间来实现线程隔离的。

public class ThreadLocal<T> {
    public T get() {
        Thread t = Thread.currentThread();
        ThreadLocal.ThreadLocalMap map = this.getMap(t);
        if (map != null) {
            ThreadLocal.ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                T result = e.value;
                return result;
            }
        }

        return this.setInitialValue();
    }
}

这里的 getEntry()方法定义在在 ThreadLocal 类中的一个静态类 ThreadLocalMap 里,传入的参数 this 是当前线程相关的上下文,因此其中的对象是线程隔离的。

static class ThreadLocalMap {
    private static final int INITIAL_CAPACITY = 16;
    private ThreadLocal.ThreadLocalMap.Entry[] table;

    private ThreadLocal.ThreadLocalMap.Entry getEntry(ThreadLocal<?> key) {
        int i = key.threadLocalHashCode & this.table.length - 1;
        ThreadLocal.ThreadLocalMap.Entry e = this.table[i];
        return e != null && e.get() == key ? e : this.getEntryAfterMiss(key, i, e);
    }
}

单例模式小结

单例模式的优点:

  • 保证内存中只有一个实例,减少了内存的开销
  • 避免对资源的多重占用
  • 设置全局访问点,严格访问控制

缺点:

  • 不是面向接口编程,单例的创建细节都在实现中
  • 扩展困难,如果要扩展单例对象,只有修改实现代码

某种程度上违背了开闭原则。

其应用场景也广泛存在于一些经典框架中。下面介绍两个例子。

Spring.AbstractBeanFactory

public abstract class AbstractFactoryBean<T>
        implements FactoryBean<T>, BeanClassLoaderAware, BeanFactoryAware, InitializingBean, DisposableBean {
    /**
     * Expose the singleton instance or create a new prototype instance.
     * @see #createInstance()
     * @see #getEarlySingletonInterfaces()
     */
    @Override
    public final T getObject() throws Exception {
        if (isSingleton()) {
            return (this.initialized ? this.singletonInstance : getEarlySingletonInstance());
        }
        else {
            return createInstance();
        }
    }

    /**
     * Determine an 'early singleton' instance, exposed in case of a
     * circular reference. Not called in a non-circular scenario.
     */
    @SuppressWarnings("unchecked")
    private T getEarlySingletonInstance() throws Exception {
        Class<?>[] ifcs = getEarlySingletonInterfaces();
        if (ifcs == null) {
            throw new FactoryBeanNotInitializedException(
                    getClass().getName() + " does not support circular references");
        }
        if (this.earlySingletonInstance == null) {
            this.earlySingletonInstance = (T) Proxy.newProxyInstance(
                    this.beanClassLoader, ifcs, new EarlySingletonInvocationHandler());
        }
        return this.earlySingletonInstance;
    }
}

MyBatis.ErrorContext

MyBatis 中的 ErrorContext 则是使用了 ThreadLocal 来实现线程范围内的单例模式,这样很好的隔离了多个线程之间的上下文与错误信息。

public class ErrorContext {

    private static final ThreadLocal<ErrorContext> LOCAL = new ThreadLocal<>();

    private ErrorContext() {}

    public static ErrorContext instance() {
        ErrorContext context = LOCAL.get();
        if (context == null) {
            context = new ErrorContext();
            LOCAL.set(context);
        }
        return context;
    }
}

最后一问

类似 Spring,MyBatis 这样的框架都广泛使用了单例模式,那他们是如何防止单例模式被反射、序列化等技术破坏的呢?

一句话的答案就是,不能防止单例模式被破坏,也不需要防止被破坏。

这个问题其实不是一个单纯的技术层面的问题。类似 Spring,MyBatis 这样的框架的定位其实是构建了一个 Bean 对象托管的生态。开发者将对象的生命周期全部托管给这些框架,从而享受这些框架提供的各种支持。

如果想绕过框架的托管,其实根本用不到反射这样的手段,只要直接手动 new 对象就好了。毕竟这个对象是你创建的,你当然完全可以不交由框架托管。但这么做也就失去了使用框架的意义。

因此,框架并不能防止单例模式被破坏,也不会阻止你去手动 new 对象。框架所保证的单例模式是指,在其生态内部,你所获得的对象遵循单例模式。

SilverLining

也可能是只程序猿

文章评论