天天看点

破坏单例模式的三种方式

单例模式的定义:

单例模式,是一种常用的软件设计模式。在它的核心结构中只包含一个被称为单例的特殊类。通过单例模式可以保证系统中,应用该模式的类一个类只有一个实例。即一个类只有一个对象实例。

在java中,单例模式我们常用的有三种(不晓得哪个天杀的说有七种,我懒得去找……)

其实,我们在日常的应用中,会遇到这么一些问题:单例模式是怎么被破坏的?单例模式无坚不摧到底好不好?单例模式既然会被破坏有没有办法写一个无法破坏的单例模式?

这一次我们会讨论关于单例模式的破坏以及单例模式的良性破坏和恶性破坏。首先我们了解一下单例模式的三种模式:

懒汉式:

最常用的一种形式,具体的代码可以参考这个:

public class IdlerSingleton {

    private static IdlerSingleton idlerSingleton;

    /**
     * 
     */
    private IdlerSingleton() {
        // constructed by Velociraptors
    }

    public static synchronized IdlerSingleton getInstance() {
        if (idlerSingleton == null) {
            idlerSingleton = new IdlerSingleton();
        }
        return idlerSingleton;
    }

}
           

饿汉式:

public class HungrySingleton {

    private static final HungrySingleton HUNGRY_SINGLETON = new HungrySingleton();

    /**
     * 
     */
    private HungrySingleton() {
        // constructed by Velociraptors
    }

    public static HungrySingleton getInstance() {
        return HUNGRY_SINGLETON;
    }

}
           

双重检锁式:

public class DoubleLockSingleton {

    private static volatile DoubleLockSingleton doubleLockSingleton;

    /**
     * 
     */
    private DoubleLockSingleton() {
        // constructed by Velociraptors
    }

    public static DoubleLockSingleton getInstance() {
        if (doubleLockSingleton == null) {
            synchronized(DoubleLockSingleton.class) {
                if(doubleLockSingleton == null) {
                    doubleLockSingleton = new DoubleLockSingleton();
                }
            }
        }
        return doubleLockSingleton;
    }

}
           

乍一看这个双重锁与懒汉模式没什么区别,但是其实双重锁的执行效率更高,而且并发效果更好。

这个模式将同步内容下放到if内部,提高了执行的效率,不必每次获取对象时都进行同步,只有第一次才同步,创建了以后就没必要了。

双重锁模式中双重判断加同步的方式,比第一个例子中的效率大大提升,因为如果单层if判断,在服务器允许的情况下,假设有一百个线程,耗费的时间为100*(同步判断时间+if判断时间),而如果双重if判断,100的线程可以同时if判断,理论消耗的时间只有一个if判断的时间。所以如果面对高并发的情况,而且采用的是懒汉模式,最好的选择就是双重判断加同步的方式。

以上三个方式执行效果都是使对象只有一个实例,然而在实际的业务需求中,并不是所有人都按照你所制定的规则玩游戏(就连吃鸡都有人开挂呢……)。

那么到底有什么情况可以破坏单例模式呢?

1.克隆(clone)

克隆破坏单例模式严格来说,并不算是破坏,打比方(注:此处比方不是人)说单例模式其实就是把类锁住,然后使用这个类的时候每次都是用的相同的一个实例,但是这样就失去了我们对于这个类的掌控,我们也许在往后的日子里偶尔会有几天,需要有多个这样子的实例,于是如果把这个类锁死了,那就没有意义了。所以我们需要做到平时锁死,但是关键时刻我们能够撬开,克隆就是这么一种良性的单例模式破坏方法,具体做法如下:

public class DoubleLockSingleton implements Cloneable {

    private static volatile DoubleLockSingleton doubleLockSingleton;

    /**
     * 
     */
    public DoubleLockSingleton() {
        // constructed by Velociraptors
    }

    public static DoubleLockSingleton getInstance() {
        if (doubleLockSingleton == null) {
            synchronized (DoubleLockSingleton.class) {
                if (doubleLockSingleton == null) {
                    doubleLockSingleton = new DoubleLockSingleton();
                }
            }
        }
        return doubleLockSingleton;
    }

    // Override by Velociraptors
    @Override
    protected Object clone() throws CloneNotSupportedException {
        // auto created by Velociraptors
        return super.clone();
    }

    public void method() {
        System.out.println("Hello DoubleLockSingleton!");
    }

}
           

测试:

public class SingletonLauncher {

    /**
     * @param args
     * @throws CloneNotSupportedException 
     */
    public static void main(String[] args) throws CloneNotSupportedException {
        // auto created by Velociraptors
        DoubleLockSingleton doubleLockSingleton = DoubleLockSingleton.getInstance();
        DoubleLockSingleton doubleLockSingleton2 = (DoubleLockSingleton)doubleLockSingleton.clone();
        if(doubleLockSingleton == doubleLockSingleton2) {
            System.out.println("Singleton break failed");
        } else {
            doubleLockSingleton.method();
            doubleLockSingleton2.method();
        }
    }

}
//Hello DoubleLockSingleton!
//Hello DoubleLockSingleton!
           

结果如下:

Hello DoubleLockSingleton! 
Hello DoubleLockSingleton!
           

正如代码所示,克隆破坏单例模式需要继承Colneable接口并且重写克隆方法,当你的代码这么写的时候意味着你为了以后破坏这个单例模式做出了准备。

2.反射(reflect)

众所周知,通过java的反射机制,何止是创建一个实例,就连映射整个java类本身的结构都易如反掌。

测试:

public class SingletonLauncher {

    /**
     * @param args
     * @throws CloneNotSupportedException
     */
    public static void main(String[] args) throws Throwable {
        // auto created by Velociraptors
        IdlerSingleton idlerSingleton = IdlerSingleton.getInstance();
        Class<?> clazz = Class.forName("com.singleton.d1213.IdlerSingleton");
        Constructor<?> constructor = clazz.getDeclaredConstructor();
        constructor.setAccessible(true);
        IdlerSingleton idlerSingleton2 = (IdlerSingleton) constructor.newInstance();
        if(idlerSingleton == idlerSingleton2) {
            System.out.println("Singleton break failed!");
        }else {
            System.out.println("Singleton break succeed!");
        }
    }

}
// Singleton break succeed!
           

执行结果如下:

Singleton break succeed!
           

这种破坏方法通过setAccessible(true)的方法是java跳过检测语法,这是不合理的,是一种恶意破坏单例此时我们的做法是:

public class IdlerSingleton {

    private static IdlerSingleton idlerSingleton;

    /**
     * 
     */
    private IdlerSingleton() {
        // constructed by Velociraptors
        synchronized (IdlerSingleton.class) {
            if (ConstantClass.idlerSingletonCount > 0) {
                // 抛出异常,使得构造方法调用中止,破坏行动被遏制
                throw new RuntimeException("This singleton pattern class can not create more object!");
            }
            ConstantClass.idlerSingletonCount++;
        }
    }

    public static synchronized IdlerSingleton getInstance() {
        if (idlerSingleton == null) {
            idlerSingleton = new IdlerSingleton();
        }
        return idlerSingleton;
    }

    /**
     * 
     * @作者 Velociraptors
     * @创建时间 下午4:35:35
     * @版本时间 2017年12月13日
     *
     */
    static class ConstantClass {

        public static int idlerSingletonCount = 0;

        /**
         * 
         */
        private ConstantClass() {
            // constructed by Velociraptors
        }

    }

}
           

测试结果:

Exception in thread "main" java.lang.reflect.InvocationTargetException
    at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance(Unknown Source)
    at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(Unknown Source)
    at java.lang.reflect.Constructor.newInstance(Unknown Source)
    at com.singleton.d1213.SingletonLauncher.main(SingletonLauncher.java:28)
Caused by: java.lang.RuntimeException: This singleton pattern class can not create more object!
    at com.singleton.d1213.IdlerSingleton.(IdlerSingleton.java:26)
    ... 5 more
           
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

成功阻挡了一波破坏。

3.序列化(serializable)

序列化也是破坏单例模式的一大利器。其与克隆性质有些相似,需要类实现序列化接口,相比于克隆,实现序列化在实际操作中更加不可避免,有些类,它就是一定要序列化。

例如下面就有一个序列化的类,并且实现了双重锁单例模式:

public class DoubleLockSingletonSerializable implements Serializable {

    /**
     * 
     */
    private static final long serialVersionUID = 972132622841L;
    private static volatile DoubleLockSingletonSerializable doubleLockSingleton;

    /**
     * 
     */
    public DoubleLockSingletonSerializable() {
        // constructed by Velociraptors
    }

    public static DoubleLockSingletonSerializable getInstance() {
        if (doubleLockSingleton == null) {
            synchronized (DoubleLockSingletonSerializable.class) {
                if (doubleLockSingleton == null) {
                    doubleLockSingleton = new DoubleLockSingletonSerializable();
                }
            }
        }
        return doubleLockSingleton;
    }

    public void method() {
        System.out.println("Hello DoubleLockSingleton!");
    }

}
           

测试:

public class SingletonLauncher {

    /**
     * @param args
     * @throws CloneNotSupportedException
     */
    @SuppressWarnings("resource")
    public static void main(String[] args) throws Throwable {
        // auto created by Velociraptors
        DoubleLockSingletonSerializable doubleLockSingletonSerializable = DoubleLockSingletonSerializable.getInstance();
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("tempFile"));
        objectOutputStream.writeObject(doubleLockSingletonSerializable);
        File file = new File("tempFile");
        ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(file));
        DoubleLockSingletonSerializable doubleLockSingletonSerializable2 = (DoubleLockSingletonSerializable) objectInputStream.readObject();
        if (doubleLockSingletonSerializable == doubleLockSingletonSerializable2) {
            System.out.println("Singleton break failed!");
        } else {
            System.out.println("Singleton break succeed!");
        }
    }

}
           

结果如下:

Singleton break succeed!
           
  • 1

通过对Singleton的序列化与反序列化得到的对象是一个新的对象,这就破坏了Singleton的单例性。

查看ObjectInputStream的源码会发现问题:其中一个名为readOrdinaryObject的方法

private Object readOrdinaryObject(boolean unshared)
        throws IOException
    {
        if (bin.readByte() != TC_OBJECT) {
            throw new InternalError();
        }

        ObjectStreamClass desc = readClassDesc(false);
        desc.checkDeserialize();

        Class<?> cl = desc.forClass();
        if (cl == String.class || cl == Class.class
                || cl == ObjectStreamClass.class) {
            throw new InvalidClassException("invalid class descriptor");
        }
        //start
        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);
        }
        //end

        passHandle = handles.assign(unshared ? unsharedMarker : obj);
        ClassNotFoundException resolveEx = desc.getResolveException();
        if (resolveEx != null) {
            handles.markException(passHandle, resolveEx);
        }

        if (desc.isExternalizable()) {
            readExternalData((Externalizable) obj, desc);
        } else {
            readSerialData(obj, desc);
        }

        handles.finish(passHandle);
        //start
        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);
            }
        }
        //end

        return obj;
    }
           

在我标注的这第一部分start-end可以清晰的看出,序列化底层还是采用了反射来破坏单例。然后在仔细看第二部分start-end,我们可以知道,序列化底层采用反射时会检查,实现了序列化接口的类是否包含readResolve,如果包含则返回true,然后会调用readResolve方法。着可好办了,想要阻止序列化破坏单例模式,就只需要声明一个readResolve方法就好了。

public class DoubleLockSingletonSerializable implements Serializable {

    /**
     * 
     */
    private static final long serialVersionUID = 972132622841L;
    private static volatile DoubleLockSingletonSerializable doubleLockSingleton;

    /**
     * 
     */
    public DoubleLockSingletonSerializable() {
        // constructed by Velociraptors
    }

    public static DoubleLockSingletonSerializable getInstance() {
        if (doubleLockSingleton == null) {
            synchronized (DoubleLockSingletonSerializable.class) {
                if (doubleLockSingleton == null) {
                    doubleLockSingleton = new DoubleLockSingletonSerializable();
                }
            }
        }
        return doubleLockSingleton;
    }

    public void method() {
        System.out.println("Hello DoubleLockSingleton!");
    }

    private Object readResolve() {
        return doubleLockSingleton;
    }

}
           

再次测试结果为:

Singleton break failed!
           

至此,我了解的一些内容就先表达到这里了,即使是简单的单例模式,如果想要深挖原理,还是有的坑让我们填的。

继续阅读