破壞單例模式的三種方式

單例模式的定義:

單例模式,是一種常用的軟件設計模式。在它的核心結構中只包含一個被稱爲單例的特殊類。通過單例模式可以保證系統中,應用該模式的類一個類只有一個實例。即一個類只有一個對象實例。

在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

成功阻擋了一波破壞。

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!

通過對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!

至此,我瞭解的一些內容就先表達到這裏了,即使是簡單的單例模式,如果想要深挖原理,還是有的坑讓我們填的。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章