1. 程式人生 > 其它 >單例模式(二)為什麼單例模式會被破壞? 怎麼解決?

單例模式(二)為什麼單例模式會被破壞? 怎麼解決?

技術標籤:java大雜燴軟體架構和設計模式破壞單例模式單例模式java序列化

文章目錄

1. 反射破壞單例模式

   假如我們特別想通過反射獲取私有的構造方法,然後再通過.newInstance()方法來建立該類的例項,就會產生兩個不同的例項。

靜態內部類建立單例模式

package patternsDesign.singleTonDetail;

public class LazyInnerClassSingleton
{ //1. 私有構造器 private LazyInnerClassSingleton() { } //2. 靜態內部類初始化類的例項 private static final class InnerClass { private static final LazyInnerClassSingleton singleTon = new LazyInnerClassSingleton(); } //3.提供訪問的方法 public static final LazyInnerClassSingleton getInstance
() { return InnerClass.singleTon; } }

通過反射破壞單例模式

package patternsDesign.singleTonDetail;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

public class Client {

    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException,
InvocationTargetException, InstantiationException { // LazyInnerClassSingleton singleton=new LazyInnerClassSingleton(); //通過反射建立例項 Class<?> clazz = LazyInnerClassSingleton.class; //通過反射來獲取私有構造方法 Constructor c = clazz.getDeclaredConstructor(null); c.setAccessible(true); //暴力初始化 Object o1 = c.newInstance(); Object o2 = c.newInstance(); System.out.println("o1=" + o1 + "\no2=" + o2); System.out.println("是否相等:" + o1 == o2); } }

列印結果:

o1=patternsDesign.singleTonDetail.LazyInnerClassSingleton@45ee12a7
o2=patternsDesign.singleTonDetail.LazyInnerClassSingleton@330bedb4
false

  由上結果可以發現,靜態內部類雖然能夠保證執行緒安全的形式保證全域性唯一,但是我們通過反射手段仍然能夠破壞單例, 因為通過反射來獲取例項時,建立了兩個不同的例項!

解決方法

   在構造方法來新增一些限制,如果存在例項,那麼就丟擲異常!

package patternsDesign.singleTonDetail;

public class LazyInnerClassSingleton {


    //1. 私有構造器
    private LazyInnerClassSingleton() {
        if (InnerClass.singleTon != null) {
            throw new RuntimeException("不允許建立多個單例!");
        }
    }

    //2. 靜態內部類初始化類的例項
    private static final class InnerClass {
        private static final LazyInnerClassSingleton singleTon = new LazyInnerClassSingleton();
    }


    //3.提供訪問的方法
    public static final LazyInnerClassSingleton getInstance() {
        return InnerClass.singleTon;
    }

}

重新執行程式,觀察結果:
在這裡插入圖片描述
由此我們可以通過在構造方法里加限制的方式,解決了反射破壞例項的問題!

2. 通過序列化破壞單例模式

靜態內部類的形式建立單例模式

package patternsDesign.seriableSingleTon;


import java.io.Serializable;

public class SerializeSingleTon implements Serializable {


    private static final long serialVersionUID = -3737338076212523007L;


    //1. 私有構造器
    private SerializeSingleTon() {
        if (InnerClass.singleTon != null) {
            throw new RuntimeException("不允許建立多個單例!");
        }
    }

    //2. 靜態內部類初始化類的例項
    private static final class InnerClass {
        private static final SerializeSingleTon singleTon = new SerializeSingleTon();
    }


    //3.提供訪問的方法
    public static final SerializeSingleTon getInstance() {
        return InnerClass.singleTon;
    }

}

通過序列化來獲取例項

package patternsDesign.seriableSingleTon;

import patternsDesign.singleTonDetail.LazyInnerClassSingleton;

import java.io.*;

public class Client {

    public static void main(String[] args) {
        SerializeSingleTon s1 = null;
        SerializeSingleTon s2 = SerializeSingleTon.getInstance();
        //通過序列化的形式破壞單例模式
        try {
            //1. 通過FileOutPutStream和ObjectOutputStream將物件寫入到檔案裡
            FileOutputStream fos = new FileOutputStream("SerializeSingleTon.obj");
            ObjectOutputStream obs = new ObjectOutputStream(fos);
            obs.writeObject(s2);
            obs.flush();
            obs.close();
            //2. 通過FileInputStream和ObjectInputStream將檔案中的物件讀入到s1中
            FileInputStream fis = new FileInputStream("SerializeSingleTon.obj");
            ObjectInputStream ois = new ObjectInputStream(fis);
            s1 = (SerializeSingleTon) ois.readObject();
            System.out.println("s1=" + s1);
            System.out.println("s2=" + s2);
            System.out.println(s1 == s2);

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

    }

}

列印結果:

s1=patternsDesign.seriableSingleTon.SerializeSingleTon@27d6c5e0
s2=patternsDesign.seriableSingleTon.SerializeSingleTon@29453f44
false

可以發現,序列化後的類出現了2個不一樣的例項!

解決方法

在類裡面新增方法private Object readResolve() { return singleTon; }

package patternsDesign.seriableSingleTon;


import java.io.Serializable;

public class SerializeSingleTon implements Serializable {

    private static SerializeSingleTon singleTon = null;


    private static final long serialVersionUID = -3737338076212523007L;


    //1. 私有構造器
    private SerializeSingleTon() {
        if (InnerClass.singleTon != null) {
            throw new RuntimeException("不允許建立多個單例!");
        }
    }

    //2. 靜態內部類初始化類的例項
    private static final class InnerClass {
        private static final SerializeSingleTon singleTon = new SerializeSingleTon();
    }


    //3.提供訪問的方法
    public static final SerializeSingleTon getInstance() {
        singleTon = InnerClass.singleTon;
        return singleTon;
    }

    // 4. 新增readResolve()方法來解決序列化破壞單例模式的問題
    private Object readResolve() {
        return singleTon;
    }

}

重新執行程式,觀察結果:

s1=patternsDesign.seriableSingleTon.SerializeSingleTon@29453f44
s2=patternsDesign.seriableSingleTon.SerializeSingleTon@29453f44
true

檢視JDK原始碼

檢視原始碼, 檢視ObjectStreamClass.java檔案,找到如下程式碼,在 ObjectInputStream 類readObject()的時候,呼叫瞭如下方法,先判斷是否有readReolve()方法

 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);
            }
        }

有的話通過反射呼叫了readResolve()方法。

   Object invokeReadResolve(Object obj)
        throws IOException, UnsupportedOperationException
    {
        requireInitialized();
        if (readResolveMethod != null) {
            try {
                return readResolveMethod.invoke(obj, (Object[]) null);
            } catch (InvocationTargetException ex) {
                Throwable th = ex.getTargetException();
                if (th instanceof ObjectStreamException) {
                    throw (ObjectStreamException) th;
                } else {
                    throwMiscException(th);
                    throw new InternalError(th);  // never reached
                }
            } catch (IllegalAccessException ex) {
                // should not occur, as access checks have been suppressed
                throw new InternalError(ex);
            }
        } else {
            throw new UnsupportedOperationException();
        }
    }

如果該方法有返回的話,就把該例項給儲存下來,通過分析JDK原始碼, 可以知道readResolve()方法能夠解決序列化破壞單例模式的問題,實際上是建立了兩個例項,而新建立的物件沒有被返回。