设计模式-单例模式

103 阅读3分钟

关键定义

一个类仅有一个实例,并提供一个全局访问点

适用场景

确保任何情况下只有一个实例

优点

  1. 减少资源开销
  2. 严格控制访问

缺点

没有抽象层,扩展性差

代码实现

懒汉式

public class LazySingleton {
    private static LazySingleton lazySingleton = null;
    private LazySingleton(){
    }
    public static LazySingleton getInstance(){
        if(lazySingleton == null){
            lazySingleton = new LazySingleton();
        }
        return lazySingleton;
    }
}

以上在多线程场景下将会有线程安全问题,下面通过双重锁检查从线程安全与性能方面优化单例模式

public class LazyDoubleCheckSingleton {
    /*禁止指令重排序*/
    private volatile static LazyDoubleCheckSingleton lazyDoubleCheckSingleton = null;
    private LazyDoubleCheckSingleton(){
    }
    public static LazyDoubleCheckSingleton getInstance(){
        if(lazyDoubleCheckSingleton == null){
            synchronized (LazyDoubleCheckSingleton.class){
                if(lazyDoubleCheckSingleton == null){
                    /*分配内存、指向内存地址、对象初始化*/
                    lazyDoubleCheckSingleton = new LazyDoubleCheckSingleton();
                }
            }
        }
        return lazyDoubleCheckSingleton;
    }
}

静态内部类

虚拟机在类的初始化阶段会获取类的初始化锁,同步多个线程对一个类的初始化,基于这个特性,可以实现基于静态内部类线程安全的延迟初始化方案(静态内部类只有当被外部类调用到的时候才会初始化)

public class StaticInnerClassSingleton {
    private static class InnerClass{
        private static StaticInnerClassSingleton staticInnerClassSingleton = new StaticInnerClassSingleton();
    }
    public static StaticInnerClassSingleton getInstance(){
        return InnerClass.staticInnerClassSingleton;
    }
    private StaticInnerClassSingleton(){
    }
}

饿汉式

public class HungrySingleton {

    private final static HungrySingleton hungrySingleton;

    static {
        hungrySingleton = new HungrySingleton();
    }

    private HungrySingleton() {

    }

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

线程单例模式

此单例模式不能保证应用全局唯一,但能保证线程唯一

public class ThreadLocalInstance {
    private static final ThreadLocal<ThreadLocalInstance> threadLocalInstanceThreadLocal = new ThreadLocal<ThreadLocalInstance>() {
        @Override
        protected ThreadLocalInstance initialValue() {
            return new ThreadLocalInstance();
        }
    };

    private ThreadLocalInstance() {

    }

    public static ThreadLocalInstance getInstance() {
        return threadLocalInstanceThreadLocal.get();
    }

}

序列化破坏单例模式

public static void main(String[] args) throws IOException, ClassNotFoundException {
        HungrySingleton instance = HungrySingleton.getInstance();
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("singleton_file"));
        oos.writeObject(instance);
        File file = new File("singleton_file");
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
        HungrySingleton newInstance = (HungrySingleton) ois.readObject();
        System.out.println(instance == newInstance);
}

以上代码执行结果为两个对象不相等,主要是因为读取对象时候执行了下面的关键逻辑

/*ObjectInputStream.java*/
private Object readOrdinaryObject(boolean unshared) throws IOException
{
   ...
   obj = desc.isInstantiable() ? desc.newInstance() : null;
   ...
    if (obj != null && handles.lookupException(passHandle) == null && desc.hasReadResolveMethod())
    {
        Object rep = desc.invokeReadResolve(obj);
       ...
    }

    return obj;
}

如果类实现了序列化接口,并且定义了以下方法,则对象为该方法返回值,否则反射新建一个对象

private Object readResolve(){
    return hungrySingleton;
}

添加上述代码之后,反序列化后的对象与原对象相等

反射攻击解决方案

对于类加载过程创建单例对象的单例模式

private HungrySingleton(){
    if(hungrySingleton != null){
        throw new RuntimeException("单例构造器禁止反射调用");
    }
}

private StaticInnerClassSingleton(){
    if(InnerClass.staticInnerClassSingleton != null){
        throw new RuntimeException("单例构造器禁止反射调用");
    }
}

以饿汉模式为例,运行以下代码后将抛出异常

public static void main(String[] args) throws IOException, ClassNotFoundException {
        Class objectClass = HungrySingleton.class;
        Constructor constructor = objectClass.getDeclaredConstructor();
        constructor.setAccessible(true);
        HungrySingleton instance = (HungrySingleton) constructor.newInstance();
        System.out.println(instance);
}

对于非类加载过程创建单例对象的单例模式,反射攻击是无法避免的

private LazySingleton(){
    if(lazySingleton != null){
        throw new RuntimeException("单例构造器禁止反射调用");
    }
}

以懒汉模式为例,即使对构造方法进行改造,但在多线程场景,先反射后创建,一样会破坏单例模式

推荐:枚举单例模式

public enum EnumInstance {
    INSTANCE;

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

反序列化时通过类型与枚举对象名称获取唯一的枚举常量,避免反序列化破坏单例模式

/*ObjectInputStream.java*/
private Enum<?> readEnum(boolean unshared) throws IOException {
    ...
    String name = readString(false);
    Enum<?> result = null;
    Class<?> cl = desc.forClass();
    if (cl != null) {
        try {
            ...
            Enum<?> en = Enum.valueOf((Class)cl, name);
            ...
        } catch (IllegalArgumentException ex) {
            ...
        }
        ...
    }
    ...
    return result;
}

反射时将会抛出无法通过反射创建枚举类型

/*Constructor.java*/
public T newInstance(Object ... initargs) {
    ...
    if ((clazz.getModifiers() & Modifier.ENUM) != 0)
        throw new IllegalArgumentException("Cannot reflectively create enum objects");
    ...
}